home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 7661 / 7661.xpi / chrome / isreaditlater.jar / content / RIL.js < prev    next >
Text File  |  2009-12-23  |  105KB  |  3,610 lines

  1. /*
  2.  
  3. License: This source code may not be used in other applications whether they
  4. be personal, commercial, free, or paid without written permission from Read It Later.
  5.  
  6.  
  7. /////////
  8. DEVELOPER API: readitlaterlist.com/api/
  9. /////////
  10.  
  11. If you would like to customize Read It Later or build an application that works with
  12. Read it Later take a look at the READ IT LATER OPEN API:
  13. http://readitlaterlist.com/api/
  14.  
  15. Suggestions for additions to Read It Later are VERY welcome.  A large number of user
  16. suggestions have been implemented.  Please let me know of any additional features you
  17. are seeking at: http://readitlaterlist.com/support/
  18.  
  19. Thanks
  20.  
  21. */
  22.  
  23. function RIL() {
  24.     this.XULnamespace = 'RIL_';
  25.     this.keyStates = [];
  26.     this.netErrors = {};
  27.     this.iconsToLoad = [];
  28.     this.inited = false;
  29.     
  30.     this.clearMarkAsReadItemsFromListIn     = 2.25         * 1000;
  31.     this.autoMarkTimerLength             = 5 * 1000;
  32.     
  33.     this.markedRowClass = this.XULnamespace + 'marked_row';
  34.     
  35.     this.tagAutoCompleteKeyTO = false;
  36. }
  37. RIL.prototype = {
  38.     
  39.     init : function() {
  40.     
  41.     try { 
  42.         
  43.         if (this.inited) return;
  44.         
  45.         dump('\n\ninit RIL')
  46.         
  47.         // get prefs service independent of the app in case .APP fails to load
  48.         this.PREFS = Components.classes['@ril.ideashower.com/rilprefs;1'].getService().wrappedJSObject;
  49.         
  50.         // Connect to app
  51.         this.APP = Components.classes['@ril.ideashower.com/rildelegate;1'].getService().wrappedJSObject;
  52.         if (!this.APP.inited) this.APP.init();
  53.         
  54.                 
  55.         if (this.APP.oldFox && this.xul('urlbar_add'))
  56.         {
  57.         this.xul('urlbar_add').hidden =
  58.         this.xul('urlbar_mark').hidden = 
  59.         this.xul('toolbar_button').hidden = true;
  60.         return;
  61.         }
  62.         
  63.         if (this.APP || this.APP.startupError)
  64.             this.addToToolbar();
  65.         
  66.         
  67.         // Events
  68.         if (this.xul('btnSync'))
  69.         this.xul('btnSync').onclick = this.clickedSync;
  70.         
  71.         
  72.                     
  73.         // Check where it's loading
  74.                        
  75.         if (this.xul('login') ||
  76.         this.xul('options') ||
  77.         this.xul('clear'))
  78.         {
  79.         // loaded in dialog
  80.         
  81.         } else if (RILLOADEDINSIDEBAR) {
  82.         
  83.         // Loaded in sidebar
  84.         this.inSidebar = true;
  85.         
  86.         } else
  87.         {
  88.         // Observers
  89.         gBrowser.addProgressListener(this.urlBarListener, Components.interfaces.nsIWebProgress.NOTIFY_STATE_ALL);        
  90.         
  91.         
  92.         // Setup XUL
  93.         this.setupKeyStrokes();
  94.         
  95.         let defaultSortIndex = this.PREFS.get('default-sort');
  96.         if (defaultSortIndex < this.xul('sort').itemCount)
  97.             this.xul('sort').selectedIndex = defaultSortIndex;
  98.         
  99.         // Update toolbar counter        
  100.         this.refreshToolbarCountStatus();
  101.         
  102.         // Add Content Menus        
  103.         this.xulId('context_bookmarkAllTabs', true).parentNode.insertBefore(this.xul('context_saveTabs', true), this.xulId('context_bookmarkAllTabs', true));
  104.         
  105.         // Context Menu Event
  106.         this.xulId("contentAreaContextMenu", true).addEventListener("popupshowing", this.contextPopupShowing, false);
  107.         
  108.         
  109.         // Page load event
  110.         let appcontent = document.getElementById("appcontent");   // browser
  111.         if(appcontent)
  112.               appcontent.addEventListener("DOMContentLoaded", this.onPageLoad, true);
  113.                       
  114.                       
  115.                 // Custom theme/platform icons                
  116.                 let skinsPrefs =  Components.classes["@mozilla.org/preferences-service;1"]
  117.                                     .getService(Components.interfaces.nsIPrefService).getBranch("general.skins.");
  118.                 let currentSkin = skinsPrefs.getCharPref('selectedSkin');
  119.         
  120.                 if (currentSkin == 'classic/1.0' && this.xul('list'))
  121.                 {
  122.                     this.xulId('main-window').setAttribute('RIL_theme', navigator.userAgent.match('Macintosh') ? 'mac' : 'win');
  123.                 }
  124.                 
  125.                 
  126.                 // Perform post install methods
  127.                 if (this.APP.openLoginWhenStarted)
  128.                 {
  129.                     this.APP.openLoginWhenStarted = false;
  130.                     this.APP.setTimeout(function(){
  131.                         this.commandInMainWindow('RIL', 'openLogin');
  132.                     }, 750);
  133.                 }
  134.                             
  135.         }
  136.         
  137.         this.inited = true;
  138.          
  139.     } catch(e)
  140.     {
  141.         
  142.         // First try to display the error nicely
  143.         try
  144.         {
  145.         this.genericMessage(this.l('couldNotStart')+":\n"+e,
  146.                    [
  147.                     {label:this.l('getHelp'), delegate:this, selector:'getHelp'}
  148.                    ], false, false, true);
  149.         
  150.         
  151.         // Hide all buttons
  152.         this.startupError = true;
  153.         
  154.         // Log it
  155.         Components.utils.reportError( this.l('couldNotStart') + "\n\n" + e );
  156.         
  157.         } catch(err2) {
  158.             
  159.         //Major failure, couldn't even display nice popups, so log it, let the user find it in the console
  160.         Components.utils.reportError( this.l('couldNotStart') + "\n\n" + e + "\n\n" + err2 );
  161.             
  162.         }
  163.     }
  164.         
  165.     },
  166.     
  167.     uninit : function() {    
  168.     if (this.inMain)  {
  169.         gBrowser.removeProgressListener( this.urlBarListener );
  170.         appcontent.removeEventListener("DOMContentLoaded", this.onPageLoad, true);
  171.     }
  172.     },
  173.     
  174.     
  175.     
  176.     
  177.     // -- Updating List
  178.     
  179.     
  180.     refreshList : function(onlyIfType) { 
  181.     if (onlyIfType && RIL.selectedListType && RIL.selectedListType != onlyIfType) return;
  182.     if (RIL.listIsOpen())
  183.     {
  184.         if (RIL.currentGenericIsPersisting) return;
  185.         RIL.refreshListStyle();
  186.         RIL.updateFilteredList();
  187.         RIL.populateList();
  188.     }
  189.     if (window.RILgr) RILgr.listWasRefreshed = true;
  190.     },
  191.     
  192.     updateFilteredList : function() {        
  193.     //custom handler
  194.         if (this.selectedListType == 'read')
  195.             return this.updateReadList();
  196.         
  197.     this.filterList();
  198.     this.sortList();
  199.     },
  200.     
  201.     updateReadList : function(force)
  202.     {   
  203.         let filter = this.getFilter();
  204.         let sort = this.getSort();
  205.         
  206.         // Check cache
  207.         if (force || !this.readList || this.readFilter != filter || this.readSort != sort || this.readPage != this.curPage || this.readListNeedsRefresh) 
  208.         {
  209.             this.readList = null;
  210.             this.filteredList = null;
  211.             this.readFilter = filter;
  212.             this.readSort = sort;
  213.             this.readPage = this.curPage;            
  214.             this.APP.SYNC.getReadList(this.curPage, filter, sort ? sort-1 : null, this.getPerPage(), this, force);
  215.         }
  216.         else
  217.         {
  218.             this.filteredList = this.readList.list;            
  219.         }
  220.         return true; //returning true stops from sort triggering
  221.     },   
  222.    
  223.     observe: function(subject, topic, data)
  224.     {        
  225.         switch(topic)
  226.         {
  227.             case('ril-api-send-finished'):
  228.                 this.APP.unregisterObserver('ril-api-send-finished', this);
  229.                 this.updateReadList(true);
  230.                 break;
  231.         }
  232.     },
  233.     
  234.     readCallback : function(request)
  235.     {        
  236.         this.readListNeedsRefresh = !request.success;
  237.         this.syncingRead = false;
  238.         this.readList = this.APP.SYNC.readCallback(request);
  239.         this.filteredList = this.readList.list;
  240.         this.refreshList();
  241.     },
  242.     
  243.     setReadListNeedsRefresh : function()
  244.     {
  245.         this.APP.d('REFRESH')
  246.         this.readListNeedsRefresh = true;  
  247.     },
  248.     
  249.     getFilter : function()
  250.     {
  251.         let filterField = this.xul('filter');
  252.         let filter = filterField.value;
  253.     
  254.     if (!filter || filter == filterField.emptyText)
  255.         filter = false; // do not filter, just return an unfiltered list;
  256.             
  257.         return filter;
  258.     },
  259.     
  260.     getSort : function()
  261.     {
  262.         return this.xul('sort').selectedItem ? this.xul('sort').selectedItem.getAttribute('value') : null;
  263.     },
  264.     
  265.     filterList : function()
  266.     {
  267.         // Read has custom handler
  268.     if (this.selectedListType == 'read')
  269.             return this.updateReadList();
  270.         
  271.     let filter = this.getFilter();
  272.     
  273.     this.filteredList = this.APP.filterList(this.selectedListType, filter, this);    
  274.         this.filteredListIndexNeedsUpdate = true;    
  275.     
  276.     return filter;
  277.     },
  278.     
  279.     sortList : function()
  280.     {
  281.         // Read has custom handler
  282.     if (this.selectedListType == 'read')
  283.             return this.updateReadList();
  284.     
  285.     // If the sort field is hidden, do not sort
  286.     if (this.xul('sort').hidden) return;
  287.     
  288.         // Sort list
  289.     let sortValue = this.getSort();    
  290.     
  291.     this.filteredList = this.APP.sortList(this.filteredList, sortValue);
  292.         this.filteredListIndexNeedsUpdate = true;
  293.     
  294.     },
  295.     
  296.     updateFilteredListIndex : function()
  297.     {
  298.         if (this.filteredListIndexNeedsUpdate || !this.filteredListIndex)
  299.         {
  300.             this.filteredListIndex = {};
  301.             for(let i in this.filteredList)
  302.             {
  303.                 this.filteredListIndex[this.filteredList[i].itemId] = i;
  304.             }
  305.         }
  306.     },
  307.     
  308.     // -- Displaying List -- //
  309.     
  310.     toggleReadingList : function() {
  311.     if ( RIL.justClosed ) { return false; }
  312.     ( !this.listIsOpen(true) ? this.openReadingList(): this.closeReadingList());    
  313.     },
  314.     
  315.     listIsOpen : function(skipSidebarCheck)
  316.     {
  317.     if ( this.xul('list') && ( !(this.xul('list').state=='closed' || this.xul('list').state=='hiding') ) )
  318.         return 'window';
  319.     
  320.     if (!skipSidebarCheck && (this.xulId('sidebar', true) && this.xulId('sidebar', true).contentWindow.location.href == "chrome://isreaditlater/content/list.xul") )
  321.         return 'sidebar';
  322.     
  323.     return false;
  324.     },
  325.     
  326.     // If the sidebar is open we need to send events there (not the active window)
  327.     // If the dropdown is showing, end events there over the sidebar
  328.     getPriorityRIL : function()
  329.     {
  330.     if (this.listIsOpen() == 'sidebar')
  331.         return this.xulId('sidebar', true).contentWindow.RIL;
  332.     else
  333.         return this;
  334.     },
  335.     
  336.     openListAfterLogin : function()
  337.     {
  338.     // make sure noautohide is fixed if it was disabled by a dialog window
  339.     setTimeout(function()
  340.         {
  341.         RIL.APP.openUrl(RIL.APP.installedUrl, false, 'tab');
  342.         RIL.openReadingList(true);
  343.         
  344.         if(RIL.APP.SYNC.syncingEnabled() && RIL.APP.PREFS.getBool('autoSync'))
  345.         RIL.APP.SYNC.sync(true);
  346.     }, 150)    
  347.     },
  348.     
  349.     openReadingList : function(forceOpen, delayedOpen) {
  350.     
  351.     if (!this.listIsOpen(true) && this.PREFS.get('list-place') == 'btn') {
  352.         
  353.         this.xul('list').openPopup( this.xul('toolbar_button', true) ? this.xul('toolbar_button', true) : this.xulId('urlbar-container', true) , "after_end", -1, -1);
  354.     
  355.     } else if (this.xulId('sidebar', true) && !RILsidebar
  356.                    /* &&
  357.            this.xulId('sidebar', true).contentWindow.location.href != "chrome://isreaditlater/content/list.xul"*/)
  358.     {
  359.         
  360.         toggleSidebar('RIL_sidebarlist', forceOpen);
  361.         if ( RIL.getSidebarWidth() < 285) { RIL.setSidebarWidth(285) }
  362.             return;
  363.     }
  364.     
  365.     
  366.     if (this.currentGenericIsPersisting) return;
  367.     
  368.     this.refreshListStyle();
  369.     this.updateFilteredList();
  370.     this.populateList();
  371.     
  372.     // --- Detecting Ctrl/Shift/Command Click on Mac --- //
  373.     if (navigator.platform != 'Win32') {
  374.         window.addEventListener("keydown", RIL.keyDown, false);
  375.         window.addEventListener("keyup", RIL.keyUp, false);
  376.         RIL.keyStates = [];
  377.     }
  378.     
  379.     
  380.     // Auto syncing
  381.     if (RIL.APP.syncWhenListIsOpened)
  382.     {
  383.         RIL.APP.syncWhenListIsOpened = false;
  384.         if (RIL.APP.PREFS.getBool('autoSync'))
  385.         {
  386.         RIL.SYNC.syncInBackgroundTillResults = true;
  387.         RIL.SYNC.sync();
  388.         }
  389.     }
  390.     
  391.     },
  392.     
  393.     closeReadingList : function()
  394.     {
  395.     if (RIL.xul('list').hidePopup) {
  396.         RIL.xul('list').hidePopup();
  397.     }
  398.     if (navigator.platform != 'Win32') {
  399.         window.removeEventListener("keydown", this.keyDown, false);
  400.         window.removeEventListener("keyup", this.keyUp, false);
  401.         }
  402.     },
  403.     
  404.     listClosing : function()
  405.     {
  406.     RIL.xul('tagAutoComplete').hidePopup();
  407.     if (RIL.messageExpiresWhenClosed || RIL.currentListClass)
  408.         RIL.clearListClass(true);
  409.     },
  410.     
  411.     refreshListStyle : function()
  412.     {
  413.     if (this.currentGenericIsPersisting) return;
  414.     this.xul('list').className = '';
  415.     this.xul('list').className += this.getListType() == 'scroll' ? ' RIL_scroll' : '';    
  416.     this.xul('listFooter').hidden = (this.getListType() == 'scroll');
  417.     },
  418.     
  419.     getListType : function()
  420.     {
  421.         return this.selectedListType == 'read' ? 'pages' : this.PREFS.get('list-type');
  422.     },
  423.     
  424.     populateList : function() {
  425.     
  426.         // Check for major errors
  427.         if (this.APP.listError)
  428.         {
  429.             this.APP.genericMessage(this.l('problemLoadingList') + " \n\n " + this.l('errorConsole'),
  430.                                    [
  431.                                     {label:this.l('getHelp'), delegate:this.APP.getTopRIL(), selector:'getHelp'}
  432.                                    ], false, false, true);
  433.             return;
  434.         }
  435.         
  436.         
  437.     // Display the list or a message?
  438.     if (this.APP.SYNC.syncing && !this.APP.SYNC.syncInBackgroundTillResults) {
  439.         this.message( this.l('Syncing') + '...' );
  440.         this.addClass( this.xul('list_inner'), 'RIL_loading' );
  441.         return;
  442.     } else if (this.syncingRead) {
  443.         this.message( this.l('GettingRead') + '...' );
  444.         this.addClass( this.xul('list_inner'), 'RIL_loading' );
  445.         return;
  446.     } else if (this.APP.LIST.fetching) {
  447.         this.message( this.l('Loading') + '...' );
  448.         this.addClass( this.xul('list_inner'), 'RIL_loading' );
  449.         return;
  450.     } else {
  451.         this.removeClass( this.xul('list_inner'), 'RIL_loading' );                
  452.     }
  453.     
  454.     
  455.     // Test if sidebar is loading correctly? - was giving errors that this method was not defined when loaded in a small popup    
  456.     if (!this.xul('listPage').removeAllItems) return;
  457.     
  458.     
  459.     // Hide editor if displayed
  460.     this.hideEditors();
  461.     
  462.     
  463.     // Special Errors
  464.     if (RIL.selectedListType == 'read' && !RIL.APP.getLogin())
  465.     {
  466.         RIL.genericMessage(RIL.l('accountForArchive') + "\n" + RIL.l('accountForArchive2'),
  467.         [{label:RIL.l('signup'),delegate:RIL,selector:'openLogin'},
  468.          {label:RIL.l('login'),delegate:RIL,selector:'openLogin'} ]
  469.         );
  470.         return;
  471.     }  
  472.     
  473.     
  474.     
  475.         if (!this.filteredList) this.updateFilteredList();
  476.     
  477.     // Clear list
  478.     this.clearChildren( RIL.xul('list_rows'), this.XULnamespace + 'row' );
  479.     
  480.     // Output special list
  481.     if (this.selectedListType == 'tags')
  482.     {
  483.         this.populateTagList();
  484.     }
  485.     
  486.     if (this.selectedListType != 'tags' || this.selectedTag)
  487.     {    
  488.         // Output list
  489.         let row, perPage, start, end;
  490.         if (this.getListType()=='pages')
  491.         {
  492.                 this.updatePageCounter();
  493.                 if (this.selectedListType == 'read')
  494.                 {
  495.                     start = 0;
  496.                     end = this.readList && this.readList.list ? this.readList.list.length : 0;
  497.                 }
  498.                 else
  499.                 {                    
  500.                     perPage = this.getPerPage();
  501.                     start = (this.curPage-1) * perPage;
  502.                     end = start + perPage * 1;
  503.                 }
  504.         }
  505.         else
  506.         {
  507.         start = 0;
  508.         end = this.filteredList.length;
  509.         }
  510.         
  511.         
  512.         // Empty list?
  513.         if (!this.filteredList || this.filteredList.length == 0)
  514.         {
  515.         let msg = '';
  516.                 
  517.         if (RIL.APP.LIST.list.length == 0)
  518.             msg = RIL.l('noItemsInList');
  519.                     
  520.         else if (RIL.xul('filter').value.length > 0 && RIL.xul('filter').value != RIL.xul('filter').emptytext)
  521.             msg = RIL.l('noItemsMatchFilter');
  522.                     
  523.         else if (RIL.selectedListType == 'current')
  524.             msg = RIL.l('noOpenItems');
  525.                     
  526.         else if (RIL.selectedListType == 'read')
  527.             msg = RIL.l('noArchivedItems');
  528.                     
  529.         RIL.genericMessage(msg, false, false, false, false, true);
  530.         }
  531.         
  532.         for(let i in this.filteredList) {        
  533.         if (i < start || i >= end) continue;
  534.         
  535.         row = this.rowForItem( this.filteredList[i], i - start );
  536.         
  537.         this.xul('list_rows').appendChild(row);        
  538.         }
  539.         
  540.         this.updatePageCounter();
  541.         
  542.     }
  543.     
  544.     this.updateLoadMore();
  545.     
  546.     // If the history observer was added (for favicon loading detection), set it to be removed soon
  547.     if (RIL.APP.historyObserverAdded)
  548.     {
  549.         clearTimeout(RIL.APP.removeHistoryObserverTO)
  550.         RIL.APP.removeHistoryObserverTO = setTimeout(RIL.APP.genericClosure(RIL.APP, 'removeHistoryObserver'), 60000);
  551.     }
  552.         
  553.     },  
  554.     
  555.     updatePageCounter : function() {
  556.     this.updatePageCounterStats();
  557.     this.fillSelect( this.xul('listPage'), 1, this.totalPages, this.curPage);
  558.     
  559.     this.xul('listPrevious').className = this.curPage == 1 ? 'RIL_dim' : 'text-link';    
  560.     this.xul('listNext').className = (this.totalPages == 0 || this.curPage == this.totalPages) ? 'RIL_dim' : 'text-link';
  561.     },
  562.     
  563.     updatePageCounterStats : function()
  564.     {
  565.         if (this.selectedListType == 'read')
  566.         {
  567.             this.totalPages = Math.ceil(this.readList.total/this.getPerPage());
  568.         }
  569.         else
  570.         {
  571.             this.totalPages = Math.ceil(this.filteredList.length/this.getPerPage());
  572.         }
  573.         if (!this.curPage) this.curPage = 1;
  574.         this.curPage = this.curPage < this.totalPages ? this.curPage : this.totalPages;
  575.         this.curPage = this.curPage > 0 ? this.curPage : 1;
  576.         return this.curPage;
  577.     },
  578.     
  579.     getPerPage : function()
  580.     {
  581.     let perPage = this.PREFS.get('list-page');
  582.     if (!perPage.match(/^[0-9]{1,3}$/))
  583.         perPage = 9;
  584.         this.perPage = perPage;
  585.     return perPage;
  586.     },
  587.     
  588.     updateLoadMore : function() { return;
  589.     /*
  590.     if (this.selectedListType == 'read') {
  591.         this.xul('listLoadMore').style.display = this.APP.SYNC.noMoreReadItems ? 'none' : 'block';
  592.         this.xul('listLoadMore').label = this.APP.SYNC.syncingRead ? '' : this.l('LoadMore');
  593.         this.xul('listLoadMore').image = this.APP.SYNC.syncingRead ? 'chrome://isreaditlater/skin/Throbber-small.gif' : '';
  594.         this.xul('listLoadMore').disabled = this.APP.SYNC.syncingRead;
  595.     }
  596.     else
  597.     {
  598.         this.xul('listLoadMore').style.display = 'none';
  599.     }*/
  600.     },
  601.     
  602.     rowForItem : function(item, rowNumber) {
  603.     
  604.         if (!item) return;
  605.         
  606.     let rowClass, row, favIcon, wrapper, subWrapper, spacer, title, titleAnchor, accessory, domain, options;
  607.     let text, edit, mark;
  608.     let item, nsURI, host, titleStr, iconUrl, days;
  609.  
  610.     nsURI = this.APP.uri(item.url);
  611.     if (!nsURI || !nsURI.host) return null;
  612.     host = nsURI.host.replace('www.','');
  613.     
  614.         let compact = this.PREFS.get('list-view') == 'cond';
  615.         
  616.     //
  617.     rowClass = this.XULnamespace + 'row ' + (compact ? this.XULnamespace + 'compact' : '');
  618.     
  619.     // Row
  620.     row = RIL.createNode('row',
  621.     {
  622.         id    : this.XULnamespace + 'item_' + item.itemId,
  623.         itemId     : item.itemId,
  624.         url        : item.url,
  625.         class    : rowClass
  626.     } );
  627.         row.onmouseover = RIL.moveMenuToRow;
  628.      
  629.     
  630.     // Favicon wrapper    
  631.     favIconWrapper = RIL.createNode('vbox',
  632.     {
  633.         class : RIL.XULnamespace + 'favIconWrapper'        
  634.     } );
  635.     
  636.     
  637.     // Favicon - lazy load icons past first page (only applies to scrollable list)
  638.         if (rowNumber < RIL.perPage)
  639.         {
  640.             favIcon = RIL.favIconElement(item, nsURI);
  641.         } else
  642.         {
  643.             RIL.iconsToLoad.push( {wrapper:favIconWrapper,item:item,uri:nsURI} );          
  644.             setTimeout( RIL.loadNextFavIcon, 100 );
  645.             favIcon = null;
  646.         }
  647.     
  648.     
  649.     // Content Wrapper        
  650.     wrapper = RIL.createNode('vbox',
  651.     {
  652.         flex    : 1
  653.     });
  654.     
  655.     
  656.     // Title Wrapper        
  657.     titleWrapper = RIL.createNode('hbox');
  658.     
  659.     // Title
  660.     titleStr = this.htmlDecode( item.title.length > 0 ? item.title : item.url );
  661.     if (!compact && titleStr.length > 120) titleStr = titleStr.substr(0,120) + '...';
  662.     
  663.     title = RIL.createNode('label',
  664.     {
  665.         flex: 1,
  666.         tooltiptext    :    item.title,
  667.         class    :    this.XULnamespace + 'item_title',
  668.         url        :    item.url,
  669.         context    :    this.XULnamespace + 'item_context'
  670.     } );
  671.     
  672.         
  673.         if (compact)
  674.         {
  675.             title.setAttribute('crop', 'end');
  676.             title.setAttribute('value', titleStr);
  677.             title.onmouseover = function(e) { RIL.showInStatusBar(null, this);    }
  678.         }
  679.         
  680.         else
  681.         {
  682.             // The title is wrapped in a html:a tag because text-decoration does not apply to multiline descriptions at the moment
  683.             titleAnchor = RIL.createNode('a',
  684.             {
  685.                 class:this.XULnamespace + 'item_title_anchor',
  686.                 style:'font-size:12px;',
  687.                 url        :    item.url,
  688.             }, true);
  689.             
  690.             RIL.wbrThisString(titleStr, titleAnchor);
  691.             
  692.             titleAnchor.onmouseover = function(e) { this.setAttribute('style', 'font-size:12px;text-decoration:underline'); RIL.showInStatusBar(null, this);    }
  693.             titleAnchor.onmouseout = function() { this.setAttribute('style', 'font-size:12px;'); }
  694.             
  695.         }
  696.         
  697.         
  698.     title.onclick         = RIL.itemClicked;
  699.     
  700.     
  701.     // Accessory
  702.     accessory = RIL.createNode('label',
  703.     {
  704.         class    :    this.XULnamespace + 'item_accessory'
  705.     } );
  706.     
  707.     if (this.selectedListType == 'current') {
  708.         accessory.textContent = item.percent + '%';
  709.     } else if (this.APP.PREFS.getBool('show-date'))
  710.         {
  711.             days = Math.round( (RIL.APP.now() - item.timeUpdated) / 60 / 60 / 24);
  712.             if (days >= 0)
  713.                 accessory.textContent = days == 0 ? 'today ' : days + ' day' + (days == 1 ? '' : 's');
  714.         } else
  715.         {
  716.         accessory.hidden = true;
  717.     }
  718.     
  719.     
  720.     // Content Wrapper
  721.         subWrapper = this.createNode('hbox', {class:this.XULnamespace+'subWrapper'});
  722.         
  723.         
  724.             
  725.         // Extras
  726.         if (!compact)
  727.         {    
  728.             // Domain
  729.             domain = this.createNode('label',
  730.             {
  731.                 flex        :    1,
  732.                 crop        :    'end',
  733.                 value        :    host,
  734.                 class        :    this.XULnamespace + 'item_domain',
  735.                 url            :    'http://' + host,
  736.                 context        :    this.XULnamespace + 'item_context',
  737.                 onclick        :    "RIL.openUrl('"+host+"');RIL.closeReadingList();"
  738.             } );
  739.             domain.onclick         = RIL.itemClicked;
  740.             domain.onmouseover = this.showInStatusBar;
  741.             
  742.             
  743.             // Spacer
  744.             spacer = RIL.createNode('spacer', {flex:1});
  745.         }
  746.        
  747.     row.appendChild( favIconWrapper );
  748.         if (favIcon)
  749.             favIconWrapper.appendChild( favIcon );
  750.     row.appendChild( wrapper );
  751.     
  752.     wrapper.appendChild( titleWrapper );
  753.     titleWrapper.appendChild( title );
  754.     if (!compact) title.appendChild( titleAnchor );
  755.     titleWrapper.appendChild( accessory );
  756.     
  757.     
  758.         if (!compact)
  759.         {
  760.             wrapper.appendChild( subWrapper );
  761.             
  762.             subWrapper.appendChild( domain );
  763.             subWrapper.appendChild( spacer );
  764.     }
  765.         
  766.         else
  767.         {
  768.             titleWrapper.appendChild( subWrapper );           
  769.         }
  770.         
  771.         
  772.     return row;
  773.     
  774.     },
  775.     
  776.     favIconElement : function(item, nsURI)
  777.     {
  778.         let iconUrl = RIL.APP.ICO.getFaviconImageForPage(nsURI).spec;
  779.         favIcon = RIL.createNode('image',
  780.         {
  781.             src    : iconUrl    
  782.         } );
  783.         if (iconUrl.match('defaultFavicon.png'))
  784.             RIL.APP.fetchFavIconForItem(item, nsURI);
  785.         return favIcon;
  786.     },
  787.     
  788.     loadNextFavIcon : function()
  789.     {
  790.         if (RIL.iconsToLoad.length)
  791.         {
  792.             let iconSet = RIL.iconsToLoad.shift();
  793.             if (iconSet.wrapper && iconSet.item && iconSet.uri)
  794.             {
  795.                 let favIcon = RIL.favIconElement(iconSet.item, iconSet.uri);
  796.                 iconSet.wrapper.appendChild(favIcon);
  797.             }
  798.         }
  799.     },
  800.     
  801.     moveMenuToRow : function(e)
  802.     {
  803.         // ---Make menu if not already built
  804.         if (!RIL.itemMenu)
  805.     {
  806.             RIL.itemMenu = {wrapper:RIL.createNode('hbox')};
  807.             
  808.             // Text
  809.             text = RIL.createNode('label',
  810.             {
  811.                 value        :    '',
  812.                 tooltiptext    :    'Text View',
  813.                 class        :    RIL.XULnamespace + 'i_text RIL_action'
  814.             } );
  815.             text.onclick = RIL.textItemClicked;
  816.             
  817.             // Edit
  818.             edit = RIL.createNode('label',
  819.             {
  820.                 value        :    '',
  821.                 tooltiptext    :    'Edit',
  822.                 class        :    RIL.XULnamespace + 'i_edit RIL_action'
  823.             } );
  824.             edit.onclick = RIL.editItem;
  825.             
  826.             // Mark as Read
  827.             mark = RIL.createNode('label',
  828.             {
  829.                 value        :    '',
  830.                 class        :    RIL.XULnamespace + 'i_x RIL_action'
  831.             } );
  832.             mark.onclick = RIL.markListItemAsRead;
  833.         
  834.             // add to menu
  835.             RIL.itemMenu.text = text;
  836.             RIL.itemMenu.edit = edit;
  837.             RIL.itemMenu.mark = mark;
  838.             
  839.             RIL.itemMenu.wrapper.appendChild( text );
  840.             RIL.itemMenu.wrapper.appendChild( edit );        
  841.             RIL.itemMenu.wrapper.appendChild( mark );   
  842.         }
  843.                
  844.         // Determine icons and tooltips
  845.         RIL.itemMenu.mark.setAttribute('tooltiptext',
  846.                                        !this.className.match('marked_row')
  847.                                        ?
  848.                                        RIL.selectedListType == 'read' ? RIL.l('AddBackToList') : RIL.l('MarkAsRead')
  849.                                        :
  850.                                        RIL.selectedListType == 'read' ? RIL.l('MarkAsRead') : RIL.l('AddBackToList')
  851.                                        );
  852.        
  853.         // Move into place
  854.         this.getElementsByClassName(RIL.XULnamespace+'subWrapper')[0].appendChild(RIL.itemMenu.wrapper);
  855.         
  856.     },
  857.     
  858.     refreshRow : function(itemId) {
  859.     let currentRow = this.xul('item_' + itemId);
  860.     if (currentRow) {
  861.         let newRow = this.rowForItem( this.APP.LIST.itemById( itemId ) );
  862.             if (newRow)
  863.                 this.xul('list_rows').replaceChild(newRow, currentRow);
  864.     }
  865.     },
  866.     
  867.     refreshTagRow : function(tags)
  868.     {    
  869.     this.APP.LIST.rebuildTagIndex();
  870.     let oldRow = this.xul('tag_' + tags.oldTag);
  871.     let currentRow = this.xul('tag_' + tags.newTag);
  872.     let newRow = this.rowForTag( tags.newTag );
  873.     
  874.     if (currentRow)
  875.     {
  876.         // new tag row already exists, which means tag was renamed to something we already have, so just refresh that row
  877.         this.xul('list_rows').replaceChild(newRow, currentRow);
  878.     }
  879.     else if (oldRow)
  880.     {
  881.         // new tag row does not exist, so replace the old one with this new one
  882.         this.xul('list_rows').replaceChild(newRow, oldRow);
  883.     }
  884.     else {
  885.         this.refreshList();
  886.     }
  887.     },
  888.     
  889.     populateTagList : function()
  890.     {        
  891.     // sort tags
  892.     this.APP.LIST.rebuildTagIndex();
  893.     
  894.     
  895.         if (this.APP.LIST.tags.length == 0)
  896.         {
  897.             RIL.genericMessage(RIL.l('noTaggedItems'), [
  898.                         {label:RIL.l('learnTags'), delegate:this, selector:'getHelpWithTags'}
  899.                        ], false, false, false, true);
  900.             
  901.             return; 
  902.         }
  903.         
  904.         
  905.     // display tags
  906.     let i, row;
  907.     for(i in this.APP.LIST.tags)
  908.     {
  909.         if (this.selectedTag && this.APP.LIST.tags[i].tag != this.selectedTag) continue;
  910.         row = this.rowForTag( this.APP.LIST.tags[i].tag );        
  911.         this.xul('list_rows').appendChild(row);
  912.     }
  913.     
  914.     },
  915.     
  916.     rowForTag : function(tag)
  917.     {
  918.     
  919.     let rowClass, row, favIcon, wrapper, subWrapper, spacer, title, titleAnchor, accessory, domain, options;
  920.     let text, edit, mark;
  921.     let item, nsURI, host, titleStr, iconUrl;
  922.     
  923.     let tagSet = this.APP.LIST.tagByTag(tag);
  924.     
  925.     //
  926.     rowClass = this.XULnamespace+'row ' + this.XULnamespace+'tagRow';
  927.     
  928.     // Row
  929.     row = this.createNode('row',
  930.     {
  931.         id        : this.XULnamespace + 'tag_' + tagSet.tag,
  932.         tag     : tagSet.tag,
  933.         class    : rowClass
  934.     } );
  935.     
  936.     
  937.     // Favicon wrapper    
  938.     favIconWrapper = this.createNode('vbox',
  939.     {
  940.         class : this.XULnamespace + 'favIconWrapper'        
  941.     } );
  942.     
  943.     
  944.     // Favicon
  945.     favIcon = this.createNode('image',
  946.     {
  947.         src    : this.selectedTag ? 'chrome://isreaditlater/skin/arrow_down.png' : 'chrome://isreaditlater/skin/arrow_right.png',
  948.         tag        :    tagSet.tag
  949.     } );
  950.     favIcon.onclick     = this.tagClicked;
  951.         
  952.         
  953.     
  954.     // Content Wrapper        
  955.     wrapper = this.createNode('hbox',
  956.     {
  957.         flex    : 1
  958.     });
  959.     
  960.     tag = this.createNode('description',
  961.     {
  962.         flex: 1,
  963.         tooltiptext    :    'View pages tagged with ' + tagSet.tag,
  964.         class    :    this.XULnamespace + 'tag_title',
  965.         tag        :    tagSet.tag
  966.     } );
  967.     
  968.     // The title is wrapped in a html:a tag because text-decoration does not apply to multiline descriptions at the moment
  969.     tagAnchor = this.createNode('a',
  970.     {
  971.         class        :this.XULnamespace + 'item_title_anchor',
  972.         style        :'font-size:12px;',
  973.         tag            :    tagSet.tag
  974.     }, true);
  975.     tagAnchor.textContent = tagSet.tag;
  976.     
  977.     tag.onclick     = this.tagClicked;
  978.     tagAnchor.onmouseover = function(e) { this.setAttribute('style', 'font-size:12px;text-decoration:underline'); }
  979.     tagAnchor.onmouseout = function() { this.setAttribute('style', 'font-size:12px;'); }
  980.     
  981.     
  982.     // Accessory
  983.     accessory = this.createNode('label',
  984.     {
  985.         class    :    this.XULnamespace + 'item_accessory'
  986.     } );
  987.     accessory.textContent = '('+tagSet.n+')';
  988.     
  989.     
  990.     // Content Wrapper        
  991.     subWrapper = this.createNode('hbox');
  992.     
  993.     
  994.     // Text
  995.     openAll = this.createNode('label',
  996.     {
  997.         value        :    '',
  998.         tooltiptext        :       this.l('openAllInTabs'),
  999.         class        :    this.XULnamespace + 'i_openAll RIL_action',
  1000.         tag            :    tagSet.tag
  1001.     } );
  1002.     openAll.onclick = this.openTagInTabs;
  1003.     
  1004.     // Edit
  1005.     edit = this.createNode('label',
  1006.     {
  1007.         value        :    '',
  1008.         tooltiptext        :    this.l('edit'),
  1009.         class        :    this.XULnamespace + 'i_edit RIL_action',
  1010.         tag            :    tagSet.tag
  1011.     } );
  1012.     edit.onclick = this.editTag;
  1013.     
  1014.     // Mark as Read
  1015.     remove = this.createNode('label',
  1016.     {
  1017.         value        :    '',
  1018.         tooltiptext        :    RIL.l('removeTag'),
  1019.         class        :    this.XULnamespace + 'i_delete RIL_action',
  1020.         tag            :    tagSet.tag
  1021.     } );
  1022.     remove.onclick = this.removeTag;
  1023.  
  1024.     
  1025.     row.appendChild( favIconWrapper );
  1026.     favIconWrapper.appendChild( favIcon );
  1027.     
  1028.     row.appendChild( wrapper );    
  1029.  
  1030.     wrapper.appendChild( tag );
  1031.     tag.appendChild( tagAnchor );
  1032.     wrapper.appendChild( accessory );
  1033.     
  1034.     
  1035.     wrapper.appendChild( subWrapper );
  1036.     
  1037.     subWrapper.appendChild( openAll );
  1038.     subWrapper.appendChild( edit );        
  1039.     subWrapper.appendChild( remove );
  1040.     
  1041.     return row;
  1042.     
  1043.     },
  1044.     
  1045.     editTag : function(e) {
  1046.     try {
  1047.     let edit_row = RIL.xul('editTag_row');
  1048.     let tag = this.getAttribute('tag');
  1049.     let row = RIL.xul('tag_'+tag);
  1050.     
  1051.     // Check if another editing instance is open
  1052.     if (!edit_row.hidden && RIL.editingRow) {
  1053.         RIL.saveTag();
  1054.         
  1055.         // Restore vis on current row
  1056.         if (RIL.editingRow) {
  1057.         RIL.editingRow.hidden = false;        
  1058.         }
  1059.     }
  1060.     
  1061.     // Toggle Visibility and move edit row into positions
  1062.     row.hidden = true;
  1063.     edit_row.hidden = false;
  1064.     row.parentNode.insertBefore(edit_row, row);
  1065.     
  1066.     // Fill fields
  1067.     RIL.xul('edit_tag').value = tag;    
  1068.     
  1069.     // Add event
  1070.     RIL.xul('edit_tag').onkeypress = RIL.checkEditTagForEnter;
  1071.     
  1072.     RIL.editingRow = row;
  1073.     RIL.editingRow.setAttribute('tag', tag);
  1074.     
  1075.     }catch(e){Components.utils.reportError(e);}
  1076.     },
  1077.     
  1078.     saveTag : function()
  1079.     {
  1080.     let oldTag = this.editingRow.getAttribute('tag');
  1081.     let newTag = RIL.xul('edit_tag').value;    
  1082.     
  1083.     // Title
  1084.     RIL.APP.LIST.renameTag(oldTag, newTag);
  1085.     
  1086.     // Update Display
  1087.     if (RIL.selectedTag == oldTag) RIL.selectedTag = newTag;
  1088.     
  1089.     //this.APP.refreshTagRowInAllOpenWindows({oldTag:oldTag, newTag:newTag});
  1090.     this.APP.refreshListInAllOpenWindows('tags');
  1091.     this.hideEditTagRow();    
  1092.     },
  1093.     
  1094.     checkEditTagForEnter : function(e)
  1095.     {
  1096.     if (e.which == 13)
  1097.         RIL.saveTag();
  1098.     },
  1099.     
  1100.     hideEditTagRow : function() {    
  1101.     this.xul('edit_tag').value = '';
  1102.     this.xul('editTag_row').hidden = true;
  1103.     this.editingRow = false;
  1104.     },
  1105.     
  1106.     openTagInTabs : function(e)
  1107.     {
  1108.     RIL.APP.LIST.rebuildTagIndex();
  1109.     
  1110.     let tag = this.getAttribute('tag');
  1111.     let tagList = RIL.APP.LIST.tagItemIndex[tag];
  1112.     let i;
  1113.     
  1114.     if (tagList)
  1115.     {
  1116.         let check = {};
  1117.         if (tagList.length < 5 ||
  1118.         RIL.PREFS.getBool('no-openAllTags-prompt') ||
  1119.         RIL.APP.PROMPT.confirmCheck( RIL.inSidebar ? RILsidebar.w : window, "Read It Later", "Are you sure you want to open all "+tagList.length+" items tagged '"+tag+"' into new tabs?", "Do not ask me again", check)
  1120.         )
  1121.         {
  1122.         for(i in tagList)
  1123.         {
  1124.             RIL.openUrl(tagList[i].url, false, 'tab');
  1125.         }
  1126.         if (check.value) RIL.PREFS.set('no-openAllTags-prompt', true);
  1127.         }
  1128.     }
  1129.     
  1130.     },
  1131.     
  1132.     removeTag : function(e, tag)
  1133.     {
  1134.     let tag = tag ? tag : this.getAttribute('tag');
  1135.     let check = {};
  1136.     if (RIL.PREFS.getBool('no-removeTag-prompt') ||
  1137.         RIL.APP.PROMPT.confirmCheck( RIL.inSidebar ? RILsidebar.w : window, "Read It Later", "Are you sure you want to delete the tag '"+tag+"'?  This action is not undoable.", "Do not ask me again", check)
  1138.         )
  1139.     {
  1140.         RIL.APP.LIST.removeTag(tag);        
  1141.         if (check.value) RIL.PREFS.set('no-removeTag-prompt', true);
  1142.         
  1143.         if (RIL.selectedTag == tag) RIL.selectedTag = false;
  1144.         RIL.APP.refreshListInAllOpenWindows();    
  1145.     }
  1146.     },
  1147.     
  1148.     tagClicked : function(e)
  1149.     {
  1150.     let tag = this.getAttribute('tag');
  1151.     if (RIL.selectedTag && RIL.selectedTag == tag)
  1152.     {
  1153.         RIL.selectedTag = false;
  1154.     } else
  1155.     {
  1156.         RIL.selectedTag = this.getAttribute('tag');
  1157.     }
  1158.     RIL.refreshList();
  1159.     },
  1160.     
  1161.     
  1162.     // -- Switching Lists -- //
  1163.     
  1164.     switchToList : function(type) {
  1165.     
  1166.     // Flush changes to the list first
  1167.     this.clearMarkAsReadItems();
  1168.     
  1169.     // Hide popups    
  1170.     this.xul('tagAutoComplete').hidePopup();
  1171.     
  1172.     // Set new list type
  1173.     this.selectedListType = type;
  1174.     
  1175.     let label, sortIsHidden;
  1176.     switch(type){
  1177.         
  1178.         case('current'):
  1179.         label = RIL.l('currentlyReading');
  1180.         sortIsHidden = true;
  1181.         break;
  1182.         
  1183.         case('read'):
  1184.         label = RIL.l('readArchive');
  1185.         sortIsHidden = false;
  1186.         break;
  1187.         
  1188.         case('tags'):
  1189.         label = RIL.l('tags');
  1190.         sortIsHidden = true;
  1191.         break;
  1192.         
  1193.         default:
  1194.         this.selectedListType = 'list';
  1195.         label = RIL.l('readingList');
  1196.         sortIsHidden = false;
  1197.         break;
  1198.         
  1199.     }
  1200.     
  1201.     this.xul('list_inner').className = this.xul('list_inner').className.replace(/RIL_listType_[a-z]*/, '');
  1202.     this.addClass( this.xul('list_inner') , 'RIL_listType_' + type);
  1203.     
  1204.     this.xul('chooser').value = label;
  1205.     this.xul('sort').hidden = sortIsHidden;
  1206.     
  1207.         this.curPage = 1; //reset page counter
  1208.     this.clearListClass();
  1209.     this.refreshList();
  1210.     
  1211.     },
  1212.     
  1213.     addListClass : function(cls, label)
  1214.     {
  1215.     RIL.clearListClass();
  1216.     RIL.currentListClass = RIL.XULnamespace + cls;
  1217.         RIL.addClass( RIL.xul('list'), RIL.currentListClass);
  1218.     if (label)
  1219.         RIL.xul('chooser').value = label;
  1220.     },
  1221.     
  1222.     clearListClass : function(resetLabel)
  1223.     {
  1224.     RIL.currentGenericIsPersisting = false;
  1225.     if (RIL.currentListClass)
  1226.         RIL.removeClass( RIL.xul('list'), RIL.currentListClass);
  1227.     if (resetLabel)
  1228.         RIL.switchToList(RIL.selectedListType);
  1229.     },
  1230.     
  1231.     
  1232.     // -- List Functions and Events -- //
  1233.     
  1234.     filterUpdated : function() {
  1235.     clearInterval(this.filterUpdateTO);
  1236.     this.filterUpdateTO = setTimeout(function() {
  1237.         RIL.curPage = 1;
  1238.         RIL.clearListClass();
  1239.         if (!RIL.filterList()) // if there is no filter, resort the list because we just grabbed a fresh slice of the LIST.list
  1240.         RIL.sortList();
  1241.         RIL.populateList();
  1242.     }, 250);
  1243.     },
  1244.     
  1245.     sortUpdated : function() {
  1246.         this.curPage = 1;
  1247.     this.sortList();
  1248.     this.populateList();
  1249.     this.PREFS.set('default-sort', this.xul('sort').selectedIndex);
  1250.     },
  1251.     
  1252.     showInStatusBar : function(e, obj) {
  1253.     obj = obj ? obj : this;
  1254.     RIL.xulId('statusbar-display', true).label = obj.getAttribute('url');
  1255.     obj.addEventListener('mouseout', function() { RIL.xulId('statusbar-display', true).label = ''; }, true);        
  1256.     },
  1257.     
  1258.     // --- //
  1259.     
  1260.     message : function(msg) {
  1261.     this.xul('listMessageText').value = msg;
  1262.     this.xul('listMessage').hidden = false;
  1263.     },
  1264.     
  1265.     genericMessage : function(msg, buttons, openWindow, chooserValue, persist, inPlaceOfList) {
  1266.         
  1267.     RIL.clearChildren(RIL.xul('genericMessage'));
  1268.     
  1269.     let parts = msg.split(/\n{1,}/g);
  1270.     let i, desc;
  1271.     for(i=0; i<parts.length; i++)
  1272.     {
  1273.         desc = RIL.createNode('description')
  1274.         desc.textContent = parts[i];
  1275.         RIL.xul('genericMessage').appendChild(desc);
  1276.     }
  1277.         
  1278.     if (buttons)
  1279.     {
  1280.         let btn;
  1281.         let buttonWrapper = RIL.createNode('hbox');
  1282.         RIL.xul('genericMessage').appendChild( buttonWrapper );
  1283.         
  1284.         for(i=0; i<buttons.length; i++)
  1285.         {
  1286.         btn = RIL.createNode('button', {label:buttons[i].label});
  1287.         btn.onclick = RIL.APP.genericClosure(buttons[i].delegate, buttons[i].selector);
  1288.         buttonWrapper.appendChild(btn);
  1289.         }
  1290.     }
  1291.     
  1292.     RIL.addListClass( !inPlaceOfList ? 'genericMessageOverAll' : 'genericMessage', chooserValue);
  1293.     
  1294.     // make sure this is below addListClass
  1295.     RIL.currentGenericIsPersisting = persist;
  1296.     
  1297.     if (openWindow && !RIL.listIsOpen())
  1298.         RIL.openReadingList();
  1299.     
  1300.     RIL.messageExpiresWhenClosed = persist == 2 ? true : !persist;
  1301.     
  1302.     },
  1303.     
  1304.     // --- //
  1305.     
  1306.     
  1307.     itemClicked : function(e, obj, item, url) {    
  1308.     var click = RIL.whatIsTheClickTarget(e, 'open');
  1309.     obj = obj ? obj : this;
  1310.  
  1311.     if (click.targ) {
  1312.         RIL.openUrl( url ? url : item ? item.url : obj.getAttribute('url'), false, click.targ);
  1313.         if (click.userSetting) {
  1314.                 RIL.closeReadingList();
  1315.         }
  1316.     }
  1317.     },
  1318.     
  1319.     textItemClicked : function(e)
  1320.     {
  1321.         // Get itemId
  1322.         let row = RIL.bubbleToTagName(this, 'row');
  1323.         let itemId = row.getAttribute('itemId');
  1324.         let item;
  1325.         
  1326.     // Needs to add item back into list if click is coming from read list.
  1327.     if (RIL.selectedListType == 'read')
  1328.     {
  1329.             item = RIL.readList.list[ RIL.readList.iByItem[ itemId ] ];
  1330.         let listItem = RIL.APP.LIST.itemByUrl( item.url );
  1331.         if (!listItem)
  1332.         {
  1333.         RIL.addReadItem( item, itemId );        
  1334.         }
  1335.     }
  1336.         else {            
  1337.             // Needs to add url and item id to self for the itemClicked function to read
  1338.             item = item ? item : RIL.APP.LIST.itemById( itemId );
  1339.         }   
  1340.  
  1341.     RIL.itemClicked(e, this, item, 'chrome://isreaditlater/content/loadText.html?url=' + item.url);
  1342.     },
  1343.     
  1344.     itemContextSetup : function() {
  1345.     let url = document.popupNode.getAttribute('url') ? document.popupNode.getAttribute('url') : document.popupNode.parentNode.getAttribute('url');
  1346.     RIL.xul('item_context').setAttribute('url', url);
  1347.     RIL.contextRow = RIL.bubbleToTagName(document.popupNode, 'row');
  1348.     RIL.xul('item_context_edit').hidden = (RIL.selectedListType == 'read');
  1349.     
  1350.     // Decide which label to show for x action
  1351.     if (!RIL.contextRow.className.match(RIL.markedRowClass) )
  1352.     {
  1353.         let label = RIL.selectedListType == 'read' ? RIL.l('AddBackToList') : RIL.l('MarkAsRead');
  1354.         RIL.xul('content_markAsRead').label = label;        
  1355.     }
  1356.     else
  1357.     {
  1358.         let label = RIL.selectedListType == 'read' ? RIL.l('MarkAsRead') : RIL.l('MarkAsUnread');       
  1359.         RIL.xul('content_markAsRead').label = label;
  1360.     } 
  1361.     },
  1362.     
  1363.     itemContextClick : function(obj, targ) {
  1364.     this.openUrl(RIL.xul('item_context').getAttribute('url'), false, targ);
  1365.     this.closeReadingList();
  1366.     },
  1367.     
  1368.     itemContextDelete : function(obj) {
  1369.     
  1370.     let itemId = RIL.contextRow.getAttribute('itemId');
  1371.     let check = {value:false};
  1372.     if (RIL.PREFS.getBool('no-delete-prompt') || RIL.APP.PROMPT.confirmCheck( RIL.inSidebar ? RILsidebar.w : window, "Read It Later", "Are you sure you want to delete without archiving?", "Do not ask me again", check)) {                
  1373.         
  1374.             if (RIL.selectedListType == 'read')
  1375.             {
  1376.                 let item = RIL.readList.list[ RIL.readList.iByItem[ itemId ] ];
  1377.                 RIL.APP.SYNC.deleteRemote(item.url, false);
  1378.             }
  1379.                 
  1380.             else
  1381.             {
  1382.                 RIL.APP.LIST.mark(itemId, false, false, true);
  1383.             }
  1384.                 
  1385.                 
  1386.         if (check.value) RIL.PREFS.set('no-delete-prompt', true);    
  1387.     }
  1388.     RIL.APP.refreshRowInAllOpenWindows(itemId);    
  1389.     
  1390.     },
  1391.     
  1392.     
  1393.     // -- Editing Items -- //
  1394.     
  1395.     editItem : function(e, context) {
  1396.     try {
  1397.     let edit_row = RIL.xul('edit_row');
  1398.     let row = context && RIL.contextRow ? RIL.contextRow : RIL.bubbleToTagName(this, 'row');
  1399.     let item = RIL.APP.LIST.itemById( row.getAttribute('itemId') );    
  1400.     
  1401.     // Check if another editing instance is open
  1402.     if (!edit_row.hidden && RIL.editingRow) {
  1403.         RIL.saveEdit();
  1404.         
  1405.         // Restore vis on current row
  1406.         if (RIL.editingRow) {
  1407.         RIL.editingRow.hidden = false;        
  1408.         }
  1409.     }
  1410.     
  1411.     // Toggle Visibility and move edit row into positions
  1412.     row.hidden = true;
  1413.     edit_row.hidden = false;
  1414.     row.parentNode.insertBefore(edit_row, row);
  1415.     
  1416.     // Fill fields
  1417.     RIL.xul('edit_title').value = item.title;    
  1418.     RIL.xul('edit_tags').value = item.tagList;
  1419.     
  1420.     // Add event
  1421.     RIL.xul('edit_title').onkeypress = RIL.checkEditTitleForEnter;
  1422.     
  1423.     RIL.editingRow = row;
  1424.     
  1425.     }catch(e){Components.utils.reportError(e);}
  1426.     },
  1427.     
  1428.     saveEdit : function() {
  1429.     let itemId = this.editingRow.getAttribute('itemId');
  1430.     
  1431.     // Title
  1432.     RIL.APP.LIST.saveTitle(itemId, RIL.xul('edit_title').value);
  1433.     
  1434.     // Tags
  1435.     RIL.APP.LIST.saveTags(itemId, RIL.xul('edit_tags').value);
  1436.     
  1437.     // Update Display
  1438.     this.xul('tagAutoComplete').hidePopup();
  1439.     this.APP.refreshRowInAllOpenWindows(itemId);
  1440.     this.hideEditRow();
  1441.  
  1442.     },
  1443.     
  1444.     checkEditTitleForEnter : function(e)
  1445.     {
  1446.     if (e.which == 13)
  1447.         RIL.saveEdit();
  1448.     },
  1449.     
  1450.     hideEditRow : function() {    
  1451.     this.xul('edit_title').value = '';
  1452.     this.xul('edit_tags').value = '';
  1453.     this.xul('edit_title').focus();
  1454.     this.xul('edit_row').hidden = true;
  1455.     this.editingRow = false;
  1456.     },
  1457.     
  1458.     hideEditors : function()
  1459.     {
  1460.     if (this.editingRow)
  1461.     {
  1462.         this.hideEditRow();
  1463.         this.hideEditTagRow();
  1464.     }
  1465.     
  1466.     },
  1467.     
  1468.     markListItemAsRead : function(e, context) {
  1469.     
  1470.     let row = context && RIL.contextRow ? RIL.contextRow : RIL.bubbleToTagName(this, 'row');
  1471.         let cls = ' ' + RIL.markedRowClass;
  1472.         let item;
  1473.         let itemId = row.getAttribute('itemId');
  1474.     
  1475.         if (RIL.selectedListType == 'read')
  1476.         {
  1477.             item = RIL.readList.list[ RIL.readList.iByItem[ itemId ] ];
  1478.         }
  1479.         else {
  1480.             item = RIL.APP.LIST.itemById( itemId );
  1481.         }
  1482.         
  1483.     if (!row) return;
  1484.     
  1485.     // If X, set row as marked    
  1486.     if (!row.className.match(cls) )
  1487.     {
  1488.         if (!RIL.markedItems) RIL.markedItems = [];
  1489.         
  1490.         RIL.markedItems[ item.itemId ] = true;
  1491.         row.className += cls;
  1492.         
  1493.     }
  1494.     
  1495.     // If +, set row as normal, remove from mark queue
  1496.     else
  1497.     {
  1498.  
  1499.         RIL.markedItems[ item.itemId ] = false;
  1500.         row.className = row.className.replace(cls, '');
  1501.         
  1502.     }    
  1503.     
  1504.     clearTimeout(RIL.clearMarkAsReadTO);
  1505.     RIL.clearMarkAsReadTO = setTimeout(RIL.APP.genericClosure(RIL,'clearMarkAsReadItems'), RIL.clearMarkAsReadItemsFromListIn);
  1506.  
  1507.     },
  1508.     
  1509.     clearMarkAsReadItems : function()
  1510.     {
  1511.     clearTimeout(RIL.clearMarkAsReadTO);
  1512.     
  1513.     // If context menu is open, wait till it's closed
  1514.     if (RIL.xul('item_context').state == 'open' || RIL.xul('item_context').state == 'showing') {
  1515.         RIL.clearMarkAsReadTO = setTimeout(RIL.APP.genericClosure(RIL,'clearMarkAsReadItems'), RIL.clearMarkAsReadItemsFromListIn);
  1516.         return;
  1517.     }
  1518.     
  1519.     
  1520.     let item, itemsModified;
  1521.     for(let itemId in this.markedItems) {
  1522.         if (this.markedItems[itemId]) {
  1523.         
  1524.         if (RIL.selectedListType == 'read') {
  1525.             
  1526.             // add to list
  1527.                     item = RIL.readList.list[ RIL.readList.iByItem[ itemId ] ];
  1528.             RIL.addReadItem(item, itemId, true);
  1529.             
  1530.         } else {
  1531.             RIL.APP.LIST.mark( itemId, true );            
  1532.         }
  1533.         itemsModified = true;
  1534.         
  1535.         }
  1536.     }
  1537.     
  1538.     this.markedItems = [];
  1539.     
  1540.     if (itemsModified) RIL.APP.LIST.endBatchAndRefresh();
  1541.     },
  1542.     
  1543.     pageMove : function(n) {
  1544.     if (!n)
  1545.     {        
  1546.         this.curPage = this.xul('listPage').selectedIndex + 1;
  1547.     }
  1548.     else
  1549.     {
  1550.         this.curPage += n;
  1551.         
  1552.         // Bracketing
  1553.         if (this.curPage < 1) { this.curPage = 1; }
  1554.         if (this.curPage > this.totalpages) { this.curPage = this.totalpages; }
  1555.     }
  1556.     
  1557.     this.xul('listPage').selectedIndex = this.curPage-1;
  1558.         RIL.APP.d('curPage: '+ this.curPage);
  1559.         if (this.selectedListType == 'read')
  1560.             this.refreshList(); 
  1561.         else
  1562.             this.populateList();
  1563.     },
  1564.     
  1565.     
  1566.     // -- Tag Auto Complete -- //
  1567.     
  1568.     populateTagAutoComplete : function( listToUse )
  1569.     {       
  1570.     let content = RIL.xul('tagAutoCompleteContent');
  1571.     let i, c=0, tempWrapper;
  1572.                 
  1573.         if (navigator.userAgent.match(/(Macintosh|Linux)/))
  1574.         {
  1575.             content.hidden = true;
  1576.             return;
  1577.         }
  1578.     
  1579.     RIL.APP.LIST.rebuildTagIndex();
  1580.     RIL.clearChildren( content );
  1581.     RIL.checkboxesForTags = {};
  1582.     
  1583.     let ac = (listToUse && listToUse.length > 0);
  1584.     let results = ac > 0 ? listToUse : RIL.APP.LIST.tags;
  1585.     
  1586.     // Top Tags
  1587.     if (!ac)
  1588.     {
  1589.         tempWrapper = RIL.createNode('vbox', {class:RIL.XULnamespace+'topWrapper'});
  1590.         
  1591.         for(i in RIL.APP.LIST.topTags)
  1592.         {
  1593.         tempWrapper.appendChild( RIL.getTagRow(RIL.APP.LIST.topTags[i].tag, true) );
  1594.         c++;
  1595.         if (c == RIL.APP.numberOfMostUsedTags) break;
  1596.         }
  1597.         
  1598.         // If there are any top tags to show, display them and the header
  1599.         if (c > 0)
  1600.         {
  1601.         content.appendChild( RIL.getTagRowHeader('most used'+':') );
  1602.         content.appendChild( tempWrapper );
  1603.         }
  1604.     }
  1605.     
  1606.     
  1607.     // All tags
  1608.     content.appendChild( RIL.getTagRowHeader( (listToUse ? 'suggestions' : 'all tags' ) +':') );
  1609.     
  1610.         let cnt = 0;
  1611.     for(i in results)
  1612.     {
  1613.         content.appendChild( RIL.getTagRow(results[i].tag, false, ac) );
  1614.             cnt++;
  1615.     }
  1616.     
  1617.     // Clear highlight
  1618.     RIL.currentTagAutoCompleteRow = null;
  1619.     RIL.currentTagAutoCompleteHighlightIndex = null;
  1620.     
  1621.     // Clear enter run
  1622.     RIL.tagAutoCompleteEnterRunStarted = false;
  1623.     
  1624.     //
  1625.     RIL.currentTagAutoCompleting = ac;
  1626.     
  1627.     // If there are no tags to display, hide the popup
  1628.     if (!results || results.length == 0 || cnt == 0)
  1629.     {
  1630.         content.hidden = true;
  1631.     }
  1632.         else
  1633.         {
  1634.             content.hidden = false;   
  1635.         }    
  1636.     
  1637.     },
  1638.     
  1639.     getTagRowHeader : function(label)
  1640.     {
  1641.     return this.createNode('label', {class:this.XULnamespace+'tagRowHeader',value:label});    
  1642.     },
  1643.     
  1644.     getTagRow : function(tag, top, ac)
  1645.     {
  1646.     let row, checkbox, label;
  1647.     row = this.createNode('hbox', {class:this.XULnamespace + 'tagRow ' + this.XULnamespace + 'top'});
  1648.     
  1649.     checkbox = this.createNode('checkbox', {
  1650.         minheight:0,
  1651.         maxheight:11,
  1652.         minwidth:0,
  1653.         isATopRow:top?1:0,
  1654.         label:tag,
  1655.         checked:(RIL.xul('edit_tags').value.match( new RegExp('(^|,)\\s*?'+RIL.APP.regexSafe(tag)+'\\s*?(,|$)', 'i') ) ? 'true' : 'false')
  1656.     });
  1657.     checkbox.onclick = this.tagCheckboxChanged;    
  1658.     
  1659.     if (!this.checkboxesForTags[tag])
  1660.         this.checkboxesForTags[tag] = [];
  1661.     this.checkboxesForTags[tag].push(checkbox);
  1662.     
  1663.     row.appendChild(checkbox);    
  1664.     
  1665.     return row;    
  1666.     },
  1667.     
  1668.     tagCheckboxChanged : function(e)
  1669.     {
  1670.     RIL.tagCheckboxAction(this);    
  1671.     },
  1672.     
  1673.     tagCheckboxAction : function(checkbox, toggleWhenDone)
  1674.     {
  1675.     // NOTE: this.checked seems to be reversed, onclick is being called before the checkbox is changed
  1676.     let tag = checkbox.label;
  1677.     let tagRegExSafe = RIL.APP.regexSafe(tag);
  1678.     let field = RIL.xul('edit_tags');
  1679.     let cursorPosition = field.selectionStart;
  1680.     let i;
  1681.     
  1682.     // Does the tag exist in the field?  (Make sure to add slashes to prevent regex issues)
  1683.     let reg = new RegExp('(^|,)\\s*?'+tagRegExSafe+'\\s*?(,|$)', 'i');
  1684.     if (field.value.match( reg ))
  1685.     {
  1686.         // Remove it
  1687.         if (checkbox.checked) {
  1688.         field.value = field.value.replace( reg, '$1 ' );
  1689.         field.value = field.value.replace(/,\s*?$/,''); //trim comma from the end
  1690.         
  1691.         cursorPosition = field.value.length; // move to end
  1692.         }
  1693.     }
  1694.     else
  1695.     {
  1696.         // Add it
  1697.         if (!checkbox.checked)
  1698.         {
  1699.         if (RIL.currentTagAutoCompleting)
  1700.         {
  1701.             if (RIL.tagAutoCompleteEnterRunStarted)
  1702.             {
  1703.             // Insert a comma and then append the word to the current cursor position
  1704.             let prefix = RIL.APP.trim(field.value.substr(0, field.selectionStart)).length ? ', ' : '';
  1705.             field.value =  field.value.substr(0, field.selectionStart) +
  1706.                     prefix + tag +
  1707.                     field.value.substr(field.selectionStart);
  1708.                     
  1709.             cursorPosition = field.selectionStart + tag.length + prefix.length;
  1710.             
  1711.             } else
  1712.             {
  1713.             // Add it in place of cursor's word and move cursor to end of word
  1714.             let prefix = (RIL.currentTagAutoCompleteStart > 0 ? ' ' : '' );
  1715.             field.value =  field.value.substr(0, RIL.currentTagAutoCompleteStart) +
  1716.                     prefix +
  1717.                     tag + 
  1718.                     field.value.substr(RIL.currentTagAutoCompleteStart + RIL.currentTagAutoCompleteLength);
  1719.                     
  1720.             cursorPosition = RIL.currentTagAutoCompleteStart + tag.length + prefix.length;
  1721.             }
  1722.             
  1723.         } else
  1724.         {
  1725.             // Add it to the end of the string and move cursor to end
  1726.             field.value += (field.value.match(/\w/) ? ', ' : '') + tag;
  1727.             cursorPosition = field.value.length;
  1728.         }
  1729.         }
  1730.         
  1731.     }
  1732.     
  1733.     // Update top tag checkbox (and vice versa) (if it exists for this tag)
  1734.     for(i in RIL.checkboxesForTags[tag])
  1735.     {
  1736.         if (RIL.checkboxesForTags[tag][i] != checkbox)
  1737.         {
  1738.         RIL.checkboxesForTags[tag][i].checked = !checkbox.checked;
  1739.         }
  1740.     }
  1741.     
  1742.     // Refocus the textbox
  1743.     field.focus();
  1744.     field.selectionStart = field.selectionEnd = cursorPosition;
  1745.     
  1746.     // Toggle the checkbox
  1747.     if (toggleWhenDone) checkbox.checked = !checkbox.checked;
  1748.     },
  1749.     
  1750.     tagAutoCompletePress : function(e)
  1751.     {
  1752.     clearTimeout(RIL.tagAutoCompleteKeyTO);
  1753.     RIL.tagAutoCompleteKeyTO = setTimeout(RIL.APP.genericDataClosure(RIL, 'tagAutoCompleteKey', e.which), 50);
  1754.     },
  1755.     
  1756.     tagAutoCompleteKey : function(key)
  1757.     {
  1758.     
  1759.     let field = RIL.xul('edit_tags');
  1760.     let updateList = true;
  1761.     
  1762.     
  1763.     // Actions based on key
  1764.     // others you might need: 188=comma, 37/39 = LR, 8 = backspace
  1765.     if (key == 13) // Enter
  1766.     {
  1767.         if (RIL.currentTagAutoCompleteRow )
  1768.         {
  1769.         let checkbox = RIL.currentTagAutoCompleteRow.getElementsByTagName('checkbox')[0];
  1770.                 
  1771.         RIL.tagCheckboxAction( checkbox, true );
  1772.         
  1773.         if (checkbox.checked) 
  1774.             RIL.tagAutoCompleteEnterRunStarted = true; // use this to append new tags checked after the first one instead of replacing
  1775.                 
  1776.         } else
  1777.         {
  1778.         clearTimeout(RIL.tagAutoCompleteKeyTO);
  1779.         RIL.saveEdit();
  1780.         return;
  1781.         }
  1782.         updateList = false;
  1783.     }
  1784.     else if (key == 40 || key == 38) // Up/Down
  1785.     {
  1786.         //move highlight row in list
  1787.         
  1788.         // get rows
  1789.         let content = RIL.xul('tagAutoCompleteContent');
  1790.         let rows = content.getElementsByTagName('hbox');
  1791.         
  1792.         // unselect previous row
  1793.         if (RIL.currentTagAutoCompleteRow)
  1794.         RIL.removeClass(RIL.currentTagAutoCompleteRow, RIL.XULnamespace + 'highlighted');
  1795.         
  1796.         
  1797.         // Update current selection
  1798.         if (RIL.currentTagAutoCompleteHighlightIndex != null && RIL.currentTagAutoCompleteHighlightIndex >= 0)
  1799.         {
  1800.         RIL.currentTagAutoCompleteHighlightIndex += key == 38 ? -1 : 1;        
  1801.         RIL.currentTagAutoCompleteHighlightIndex = RIL.currentTagAutoCompleteHighlightIndex >= rows.length ? rows.length -1 : RIL.currentTagAutoCompleteHighlightIndex;
  1802.         RIL.currentTagAutoCompleteHighlightIndex = RIL.currentTagAutoCompleteHighlightIndex <= 0 ? 0 : RIL.currentTagAutoCompleteHighlightIndex;
  1803.         } else {
  1804.         RIL.currentTagAutoCompleteHighlightIndex = 0;
  1805.         }
  1806.         
  1807.         // highlight new row
  1808.         RIL.currentTagAutoCompleteRow = rows[RIL.currentTagAutoCompleteHighlightIndex];
  1809.         RIL.addClass(RIL.currentTagAutoCompleteRow, RIL.XULnamespace + 'highlighted');
  1810.         
  1811.         // scroll to row
  1812.         // this is a little more involved because contentRow.offsetTop always returns 0, so we have to guess at
  1813.         // it based on the height of the content area vs. number of rows
  1814.         let rowHeight = content.scrollHeight / rows.length;
  1815.         let scrollPadding = rowHeight * 3; //number of rows to buffer when scrolling
  1816.         let viewableTop = content.clientHeight + content.scrollTop;
  1817.         let positionOfRow = rowHeight * RIL.currentTagAutoCompleteHighlightIndex+1;
  1818.         if (key == 40 && positionOfRow > viewableTop - scrollPadding)
  1819.         {
  1820.         // needs to scroll down
  1821.         content.scrollTop = positionOfRow;
  1822.         }
  1823.         else if (key == 38 && positionOfRow - scrollPadding < content.scrollTop)
  1824.         {
  1825.         content.scrollTop = positionOfRow;
  1826.         }
  1827.         
  1828.         return; // no need to update search
  1829.     }
  1830.     
  1831.     // Determine which word the cursor is on (in between commas)
  1832.     let previousCommaPosition = field.value.lastIndexOf(',', field.selectionStart-1);
  1833.     let nextCommaPosition = field.value.indexOf(',', field.selectionStart);
  1834.     let start = previousCommaPosition != -1 ? previousCommaPosition+1 : 0;
  1835.     let length = nextCommaPosition != -1 ? nextCommaPosition - start : field.value.length - start;
  1836.     let word = RIL.APP.trimLeft(field.value.substr( start , length ));
  1837.     
  1838.     // Save indexes for word
  1839.     RIL.currentTagAutoCompleteStart = start;
  1840.     RIL.currentTagAutoCompleteLength = length;
  1841.     RIL.currentTagAutoCompleteWord = word;
  1842.     
  1843.     
  1844.     // Run search
  1845.     if (updateList)
  1846.     {
  1847.         if ( RIL.APP.trim(word).length > 0 )
  1848.         {
  1849.         RIL.updateTagAutoCompleteFilter(word);
  1850.         
  1851.         // Repopulate list
  1852.         RIL.populateTagAutoComplete( RIL.previousTagAutoCompleteResults );
  1853.         } else
  1854.         {
  1855.         RIL.populateTagAutoComplete(  );
  1856.         }
  1857.     }
  1858.     },
  1859.     
  1860.     updateTagAutoCompleteFilter : function(word)
  1861.     {
  1862.     let results, i, set;
  1863.     
  1864.     if (RIL.previousTagAutoCompleteWordRegexSafe && word.match( new RegExp('^' + RIL.previousTagAutoCpreviousTagAutoCompleteWordRegexSafeompleteWord, 'i' ) ))
  1865.     {        
  1866.         // reuse previous results
  1867.         results = RIL.previousTagAutoCompleteResults;    
  1868.     }
  1869.     else
  1870.     {
  1871.         // Search list for tags
  1872.         results = [];
  1873.         for(i in RIL.APP.LIST.tags)
  1874.         {
  1875.         set = RIL.APP.LIST.tags[i];
  1876.         if (set.tag.match( new RegExp('^' + RIL.APP.regexSafe(word), 'i') ))
  1877.         {
  1878.             results.push( {tag:set.tag, n:set.n} );
  1879.         }
  1880.         }
  1881.     }
  1882.     
  1883.     RIL.previousTagAutoCompleteResults = results;
  1884.     RIL.previousTagAutoCompleteWordRegexSafe = RIL.APP.regexSafe(word);
  1885.     
  1886.     },
  1887.     
  1888.     
  1889.     // -- Main Toolbar Button -- //
  1890.     
  1891.     readSomething : function()
  1892.     {
  1893.     if (!this.filteredList) this.updateFilteredList();
  1894.     
  1895.  
  1896.     // Open the reading list (if no items, there was a problem starting, or if that is the setting the user has)
  1897.     if (!this.filteredList ||
  1898.         this.filteredList.length == 0 ||
  1899.         this.PREFS.get('read') == 'list' ||
  1900.         this.startupError)
  1901.     {        
  1902.         this.openReadingList();
  1903.     }
  1904.     else
  1905.     {
  1906.         switch( this.PREFS.get('read') )
  1907.         {
  1908.         
  1909.         case('next'):
  1910.             this.readNextItemInList();
  1911.             break;
  1912.         
  1913.         case('rand'):
  1914.             this.readSomethingRandom();
  1915.             break;
  1916.         
  1917.         }
  1918.     }
  1919.     
  1920.     },
  1921.     
  1922.     readNextItemInList : function() {
  1923.     
  1924.     if (this.filteredList && this.filteredList.length > 0)
  1925.     {
  1926.             this.updateFilteredListIndex();
  1927.         
  1928.             let i;
  1929.         let currentItem = RIL.getItemForCurrentPage();
  1930.         if (currentItem.item)
  1931.         {
  1932.         // Current page is in the list, so let's move from here forward    
  1933.         
  1934.         i = this.filteredListIndex[ currentItem.item.itemId ];
  1935.         let nextItem = this.filteredList[i*1+1];
  1936.         
  1937.         if (nextItem) this.openUrl( nextItem.url );
  1938.         else this.openUrl( this.filteredList[0].url ); // first item        
  1939.                 
  1940.         }
  1941.             else {
  1942.                 if (content.document.RIL_item)
  1943.                 {
  1944.                     // Current page is no longer in list but was, so use previous index value (since the adjacent item will have moved down into it's spot)
  1945.                     i = content.document.RIL_item.oldFilteredIndex;
  1946.                 }
  1947.                 else
  1948.                 {
  1949.                     // Current page is not in list, so open the first item in the list
  1950.                     i = 0;
  1951.                 }
  1952.                 
  1953.                 // if list still hasn't been opened since removal, filteredList has not been updated, so update it if we
  1954.                 // find that the old entry still exists in the list
  1955.                 if (!RIL.APP.LIST.itemByUrl(this.filteredList[i].url))
  1956.                     this.updateFilteredList();
  1957.                 
  1958.                 this.openUrl( this.filteredList[i].url );
  1959.             }
  1960.         }
  1961.     },
  1962.     
  1963.     readSomethingRandom : function() {
  1964.     if (this.filteredList.length > 0)
  1965.     {
  1966.         let i = Math.floor(Math.random() * this.filteredList.length);
  1967.         this.openUrl( this.filteredList[i].url );
  1968.     }
  1969.     },
  1970.         
  1971.    
  1972.  
  1973.  
  1974.     // -- Logins -- //
  1975.     
  1976.     openLogin : function()
  1977.     {
  1978.     if (RIL.listIsOpen())
  1979.         RIL.closeReadingList();
  1980.     window.openDialog("chrome://isreaditlater/content/login.xul", "", "chrome,titlebar,toolbar,centerscreen,resizable");    
  1981.     },
  1982.     
  1983.     relogin : function()
  1984.     {
  1985.     RIL.APP.logout(true);
  1986.         RIL.closeReadingList()
  1987.         RIL.openLogin();
  1988.     },
  1989.     
  1990.     openToRSSFeed : function()
  1991.     {
  1992.     let login = this.APP.getLogin();
  1993.     window.open('http://readitlaterlist.com/users/'+login.username+'/');
  1994.     },
  1995.     
  1996.     
  1997.     // -- //
  1998.     
  1999.     getHelp : function()
  2000.     {
  2001.     window.open('http://readitlaterlist.com/support/');
  2002.     },
  2003.     
  2004.     getHelpWithTags : function()
  2005.     {
  2006.         window.open('http://readitlaterlist.com/support/tags/');
  2007.     },
  2008.     
  2009.     
  2010.     // -- Options -- //
  2011.     
  2012.     openOptions : function(tab)
  2013.     {
  2014.     if (RIL.listIsOpen())
  2015.         RIL.closeReadingList();
  2016.     window.openDialog("chrome://isreaditlater/content/options.xul", "", "chrome,titlebar,toolbar,centerscreen,resizable", tab);
  2017.     },
  2018.     
  2019.     
  2020.     // -- Sync -- //
  2021.     
  2022.     clickedSync : function(e)
  2023.     {
  2024.     if (e.button != 0)
  2025.     {       
  2026.         e.stopPropagation();            
  2027.     }
  2028.     else if (RIL.APP.SYNC.syncing && !RIL.APP.SYNC.syncInBackgroundTillResults)
  2029.         {
  2030.             // cancel the sync
  2031.             RIL.APP.SYNC.cancelSync();
  2032.         }
  2033.         else
  2034.     {
  2035.         RIL.APP.SYNC.sync(false, true);
  2036.     }
  2037.     },
  2038.     
  2039.     
  2040.     onContextSync : function()
  2041.     {
  2042.         let syncing = (this.APP.SYNC.syncing && !this.APP.SYNC.syncInBackgroundTillResults);        
  2043.         RIL.xul('context_cancelSync').hidden = !syncing;
  2044.         RIL.xul('context_normalSync').hidden = 
  2045.         RIL.xul('context_fullSync').hidden = syncing;     
  2046.     },
  2047.     
  2048.     
  2049.     // -- Offline -- //  
  2050.     
  2051.     openOfflineWindow : function()
  2052.     {
  2053.     let xulRIL = this.getPriorityRIL();
  2054.     
  2055.     xulRIL.xul('offlineOptionDownloadWeb').checked = RIL.PREFS.getBool('getOfflineWeb');
  2056.     xulRIL.xul('offlineOptionDownloadText').checked = RIL.PREFS.getBool('getOfflineText');
  2057.     
  2058.     xulRIL.addListClass( 'offline', 'Go Offline' );
  2059.     
  2060.     if (RIL.APP.OFFLINE.clearingOffline)
  2061.         xulRIL.offlineIsClearing();
  2062.         
  2063.     else if (RIL.APP.OFFLINE.movingOffline)
  2064.         xulRIL.offlineIsMoving();
  2065.     
  2066.     else if (RIL.APP.OFFLINE.downloading)
  2067.         xulRIL.offlineIsDownloading();
  2068.         
  2069.     
  2070.     if (!RIL.listIsOpen())
  2071.         xulRIL.openReadingList();
  2072.     },
  2073.     
  2074.     addNewItemToDownload : function(item, skipDownloadViews)
  2075.     {
  2076.     if (RIL.PREFS.getBool('autoOffline') && item)
  2077.     {
  2078.         let views = null;
  2079.         if (skipDownloadViews)
  2080.         {
  2081.         views = {web:RIL.PREFS.getBool('getOfflineWeb'), text:RIL.PREFS.getBool('getOfflineText')};
  2082.         if (skipDownloadViews.web && views.web) views.web = false;
  2083.         if (skipDownloadViews.text && views.text) views.text = false;
  2084.         }
  2085.        
  2086.         RIL.APP.OFFLINE.addItemToQueue( item, views, true );
  2087.     }
  2088.     },
  2089.     
  2090.     offlineStart : function(xulRIL)
  2091.     {
  2092.     // launch process from main window window (not sidebar)
  2093.     let mainWindow = RIL.APP.getMainWindow();
  2094.     xulRIL = xulRIL ? xulRIL : RIL;
  2095.                
  2096.     if (mainWindow != window)
  2097.     {
  2098.         if (mainWindow.RIL)
  2099.         mainWindow.RIL.offlineStart(RIL);
  2100.         return;
  2101.     }
  2102.     
  2103.     // Make sure at least something is checked
  2104.     if (!xulRIL.xul('offlineOptionDownloadWeb').checked &&
  2105.         !xulRIL.xul('offlineOptionDownloadText').checked) {
  2106.         
  2107.         RIL.APP.PROMPT.alert(window, 'Read It Later', xulRIL.l('offlineNeedsOneView'));
  2108.         
  2109.         return false;
  2110.     }
  2111.     
  2112.     // Save options
  2113.     RIL.PREFS.set('getOfflineWeb', xulRIL.xul('offlineOptionDownloadWeb').checked);    
  2114.     RIL.PREFS.set('getOfflineText', xulRIL.xul('offlineOptionDownloadText').checked);
  2115.     
  2116.     
  2117.     // Carry on    
  2118.     let status, message;
  2119.     let action;
  2120.     
  2121.     if (!navigator.onLine)
  2122.     {
  2123.         message = xulRIL.l('mustBeOnlineToDownload')+"\n"+xulRIL.l('turnOffWorkOffline');
  2124.         action = {label:xulRIL.l('tryAgain'), delegate:xulRIL, selector:'openOfflineWindow'};
  2125.     }
  2126.     else
  2127.     {    
  2128.     
  2129.         status = RIL.APP.OFFLINE.start(true);
  2130.         if (status == -2) // queue is empty
  2131.         {        
  2132.         if (RIL.APP.LIST.list.length == 0)
  2133.         {
  2134.             message = xulRIL.l('nothingToDownload')+"\n"+xulRIL.l('addPagesToYourList');
  2135.         } else {
  2136.             xulRIL.offlineDone();
  2137.             return false;
  2138.         }
  2139.         action = null;
  2140.         
  2141.         } else {
  2142.         
  2143.         return xulRIL.offlineIsDownloading();
  2144.         
  2145.         }
  2146.         
  2147.     }
  2148.     
  2149.     xulRIL.genericMessage(message, [action]);
  2150.         
  2151.     },
  2152.     
  2153.     updateDownloadProgress : function(na)
  2154.     {
  2155.     
  2156.     if (na[0] == -1) {
  2157.         this.xul('offlineProgress').hidden = true;
  2158.         return;
  2159.     }
  2160.     
  2161.         if (na[0] <= na[1])
  2162.     {
  2163.         clearInterval( this.hideProgressTO ); // make sure it isn't hidden now that it's been updated
  2164.             if (this.xul('offlineProgress').hidden) this.xul('offlineProgress').hidden = false;            
  2165.             this.xul('offlineProgress').label = RIL.l('downloading') + ' '+na[0]+'/'+na[1];
  2166.         this.xul('offlineProgress').onclick = RIL.offlineIsDownloading;
  2167.         }
  2168.     else
  2169.     {
  2170.             this.xul('offlineProgress').label = RIL.l('downloadingComplete');
  2171.         this.xul('offlineProgress').onclick = RIL.offlineDone;
  2172.         clearInterval( this.hideProgressTO );
  2173.             this.hideProgressTO = setTimeout( function(){        
  2174.         RIL.xul('offlineProgress').hidden=true;
  2175.         }, 10000);
  2176.                 
  2177.         xulRIL = RIL.getPriorityRIL();
  2178.         if (xulRIL.listIsOpen() && xulRIL.currentListClass == xulRIL.XULnamespace + 'genericMessageOverAll')
  2179.         xulRIL.offlineDone();
  2180.         }
  2181.     },
  2182.     
  2183.     offlineDone : function()
  2184.     {
  2185.     let f   =     RIL.APP.OFFLINE.counters.failed;
  2186.     let msg =     RIL.l('hasBeenDownloaded') + "\n";
  2187.     if (f)
  2188.         msg +=    f + ' page' + (f==1?' was':'s were') + " not downloaded because they could not be reached.\n";
  2189.     
  2190.     RIL.getPriorityRIL().genericMessage(msg, false, true, 'Go Offline', 2);
  2191.     },
  2192.     
  2193.     offlineCancel : function() {
  2194.     RIL.APP.OFFLINE.downloading = false;
  2195.     
  2196.     if (RIL.listIsOpen())
  2197.         RIL.getPriorityRIL().openOfflineWindow();
  2198.         
  2199.     RIL.APP.OFFLINE.cancel();
  2200.     },
  2201.     
  2202.     offlineIsDownloading : function()
  2203.     {    
  2204.     RIL.getPriorityRIL().genericMessage(RIL.l('beingDownloaded') + ".\n" + RIL.l('doSomethingElse'), 
  2205.     [{label:RIL.l('stopDownload'), delegate:RIL, selector:'offlineCancel'}]
  2206.     , true, RIL.l('goOffline'), 2);
  2207.     },  
  2208.     
  2209.     offlineIsClearing : function()
  2210.     {    
  2211.     this.getPriorityRIL().genericMessage(RIL.l('cacheBeingCleared'), 
  2212.     null, true, RIL.l('goOffline'));
  2213.     },  
  2214.     
  2215.     offlineIsMoving : function()
  2216.     {    
  2217.     this.getPriorityRIL().genericMessage(RIL.l('cacheBeingMoved'), 
  2218.     null, true, RIL.l('goOffline'));
  2219.     },  
  2220.        
  2221.        
  2222.     // -- //
  2223.        
  2224.        
  2225.     offlineNotification : function(appendMsg)
  2226.     {
  2227.     if (!this.findNotificationBox('offline')) this.offlineNotificationOff();
  2228.     
  2229.     let msg = RIL.l('pageNotOffline');
  2230.     RIL.currentOfflineNotification = this.offlineNotificationOn( msg + (appendMsg ? appendMsg : '') );
  2231.     },
  2232.     
  2233.     offlineNotificationOff : function()
  2234.     {
  2235.     RIL.removeNotificationBox();
  2236.     RIL.currentNotification = null;
  2237.     },
  2238.     
  2239.     offlineNotificationOn : function(msg)
  2240.     {
  2241.     let name    = RIL.XULnamespace + 'offline';
  2242.     let icon    = 'chrome://isreaditlater/skin/disconnect.png';
  2243.     buttons = [{
  2244.             accessKey : '',
  2245.             label:  RIL.l('moreInfo'),
  2246.             popup: null,
  2247.             callback: function(){content.document.location.href='chrome://isreaditlater/content/offline.html';}
  2248.         },{
  2249.             accessKey : '',
  2250.             label:  RIL.l('hide'),
  2251.             popup: null,
  2252.             callback: this.offlineNotificationOff
  2253.         }];
  2254.     
  2255.     return this.getNotificationBox(name, msg, icon, buttons);
  2256.     },
  2257.     
  2258.     //    
  2259.     
  2260.     netErrorNotification : function(appendMsg)
  2261.     {
  2262.     if (!this.findNotificationBox('offline')) this.netErrorNotificationOff();
  2263.     
  2264.     let msg = RIL.l('loadedOffline');
  2265.     RIL.currentOfflineNotification = this.netErrorNotificationOn( msg + (appendMsg ? appendMsg : '') );
  2266.     },
  2267.     
  2268.     netErrorNotificationOff : function()
  2269.     {
  2270.     RIL.removeNotificationBox();
  2271.     RIL.currentNotification = null;
  2272.     },
  2273.     
  2274.     netErrorNotificationOn : function(msg)
  2275.     {
  2276.     let name    = RIL.XULnamespace + 'offline';
  2277.     let icon    = 'chrome://isreaditlater/skin/disconnect.png';
  2278.     buttons = [{
  2279.             accessKey : '',
  2280.             label:  'Hide',
  2281.             popup: null,
  2282.             callback: this.netErrorNotificationOff
  2283.         }];
  2284.     
  2285.     return this.getNotificationBox(name, msg, icon, buttons);
  2286.     },
  2287.     
  2288.     
  2289.     // -- Location bar XUL -- //
  2290.     
  2291.     checkLoad : function(e) {
  2292.     if (e.originalTarget instanceof HTMLDocument) {
  2293.         var doc = e.originalTarget;
  2294.         if (e.originalTarget.defaultView.frameElement) {
  2295.         // The unload was being called from an iframe, ignore it
  2296.         return false;
  2297.         }
  2298.         RIL.checkPage();
  2299.     }
  2300.     },
  2301.     
  2302.     checkPage : function(document)
  2303.     {    
  2304.     let url = RIL.currentURL();
  2305.     let item;
  2306.  
  2307.     if (url != 'about:blank' && RIL.APP.listHasBeenLoadedOnce && (RIL.APP.checkIfValidUrl(url) || url.match(/^(file|chrome):/))) {        
  2308.  
  2309.         RIL.currentItem = RIL.getItemForCurrentPage();        
  2310.         item = RIL.currentItem.item;
  2311.         
  2312.         if (item)
  2313.         {
  2314.         
  2315.         if ((!navigator.onLine || (document && document.netError)) && !RIL.currentItem.offline)
  2316.         {
  2317.             if (item.offlineWeb)
  2318.             {
  2319.             RIL.loadOfflineWebView( item.itemId );
  2320.             }
  2321.             else if (item.offlineText)
  2322.             {
  2323.             RIL.openTextViewForUrl( item.url );            
  2324.             }
  2325.         }
  2326.         
  2327.         // Mask url for offline files
  2328.         if (RIL.currentItem.type == -1 || RIL.currentItem.offline )
  2329.         {
  2330.             RIL.xulId('urlbar').value = item.url;
  2331.             
  2332.             if (navigator.onLine && RIL.netErrors[ item.itemId ])
  2333.             {
  2334.             RIL.netErrorNotification();
  2335.             RIL.netErrors[ item.itemId ] = false;
  2336.             }
  2337.         }
  2338.         
  2339.         // start auto mark timer
  2340.         if (RIL.PREFS.getBool('autoMark') && (RIL.lastURL && item.url != RIL.lastURL))
  2341.         {
  2342.             clearTimeout(RIL.autoMarkTimerTO);
  2343.             RIL.autoMarkItemId = item.itemId;
  2344.             RIL.autoMarkTimerTO = setTimeout(RIL.autoMark, RIL.autoMarkTimerLength);
  2345.         }
  2346.         
  2347.         
  2348.         }
  2349.         
  2350.     }
  2351.  
  2352.     RIL.updateLocationBarButtons( item ? 2 : 1 );
  2353.     
  2354.     return item;
  2355.     },
  2356.     
  2357.     checkDocument : function(document)
  2358.     {
  2359.     if (!document) return;
  2360.     let location = document.location;    
  2361.     
  2362.     if (location != 'about:blank') {
  2363.         
  2364.         RIL.currentItem = RIL.getItemForDocument(document);        
  2365.         let item = RIL.currentItem.item;
  2366.         
  2367.         if (item)
  2368.         {
  2369.         
  2370.         if ((!navigator.onLine || (document && document.netError)) && !RIL.currentItem.offline)
  2371.         {        
  2372.          
  2373.             if (item.offlineWeb == 1)
  2374.             {
  2375.             RIL.loadOfflineWebView( item.itemId, document );
  2376.             }
  2377.             else if (item.offlineText == 1)
  2378.             {
  2379.             RIL.openTextViewForUrl( item.url, null, document );            
  2380.             }
  2381.             else if (document && document.netError && document == content.document) // only show if the document is the same as the neterror doc?
  2382.             {            
  2383.             RIL.offlineNotification( item.offlineWeb == -1 || item.offlineText == -1 ? RIL.l('unableToReachLastTime') : null );
  2384.             }
  2385.          
  2386.         }
  2387.         
  2388.         return item;
  2389.         
  2390.         }
  2391.     }
  2392.     
  2393.     },
  2394.     
  2395.     onPageLoad : function(aEvent) {
  2396.     if (aEvent.originalTarget.nodeName == "#document") {
  2397.         let doc = aEvent.originalTarget;
  2398.         
  2399.             if (doc.defaultView.frameElement) return false;
  2400.         if (!doc || !doc.location) return false;
  2401.         
  2402.         // Handle Redirections
  2403.         if (doc.location.href.match(/chrome\:\/\/isreaditlater\/content\/loadText.html\?url=(.*)/))
  2404.         {
  2405.         RIL.retrieveAndOpenTextForUrl(RegExp.$1, 'current', doc);
  2406.         return;
  2407.         }
  2408.         
  2409.         // -- Handle Plugins
  2410.                 
  2411.         // Google Reader
  2412.         RILgr.check();
  2413.            
  2414.         
  2415.         
  2416.         let currentItem = RIL.getItemForUri( RIL.APP.uri(doc.location), doc );
  2417.         let item = currentItem.item;
  2418.         
  2419.         if (item) {
  2420.         
  2421.         
  2422.         // Stop looking for a net error if the page loaded normally
  2423.         RIL.checkForNetError(doc);
  2424.         
  2425.         // Text Error Page
  2426.         if (currentItem.type == -1)
  2427.         {        
  2428.             
  2429.             if (doc.body)
  2430.             {
  2431.             let error = RIL.APP.errorPackages[item.itemId];
  2432.             doc.title = item.title;
  2433.             doc.getElementById('RIL_title').innerHTML = item.title;
  2434.             doc.getElementById('RIL_original').innerHTML = item.url;
  2435.             doc.getElementById('RIL_original').href = item.url;
  2436.             doc.getElementById('RIL_message').innerHTML = error.errorMessage;
  2437.             }
  2438.             
  2439.             return;
  2440.         }
  2441.         
  2442.         // scroll to
  2443.         let scrollSet = item && RIL.currentItem && item.scroll ? item.scroll[ RIL.currentItem.type ] : null;
  2444.         if (scrollSet)
  2445.         {
  2446.             
  2447.             if (currentItem.type == 1 && doc && doc.getElementById('RIL_less'))
  2448.             {
  2449.             
  2450.             // find first Element with attribute nodeIndex = i (make sure it's inside less or more based on view)
  2451.             let i, e, av, arrElements;
  2452.             let mode = scrollSet.section == 1 ? 'more' : 'less';
  2453.             arrElements = doc.getElementById('RIL_'+mode).getElementsByTagName('*');
  2454.             
  2455.             for(i=0; i<arrElements.length; i++){
  2456.                 av = arrElements[i].getAttribute('nodeIndex');
  2457.                 if (av && av == item.scroll[1].nodeIndex) {
  2458.                 e = arrElements[i];
  2459.                 break;
  2460.                 }
  2461.             }
  2462.             
  2463.             if (e)
  2464.             {
  2465.                 RIL.APP.d(mode);
  2466.                 doc.body.setAttribute('id', mode);
  2467.                 e.scrollIntoView(true);
  2468.             }
  2469.             
  2470.             }
  2471.             else if ( currentItem.type == 2 && item.scroll[2] )
  2472.             {
  2473.             doc.defaultView.scrollTo( content.pageXOffset, item.scroll[2].nodeIndex );            
  2474.             }
  2475.         }
  2476.         
  2477.         // watch scrolling
  2478.         RIL.startWatchingScrollingForItemId(item.itemId, doc);
  2479.         
  2480.         // offline view
  2481.         if (currentItem.offline)
  2482.         {
  2483.             
  2484.             // text view
  2485.             if (currentItem.type == 1)
  2486.             {
  2487.             // setup events
  2488.             if (doc.body)
  2489.             {
  2490.                 
  2491.                 // load saved text view settings
  2492.                 doc.body.setAttribute('o', RIL.PREFS.get('text-options'));
  2493.                 
  2494.                 let evt = doc.createEvent("Events");
  2495.                 evt.initEvent("settingsloaded", true, false);
  2496.                 doc.body.dispatchEvent(evt);
  2497.                 
  2498.                 doc.addEventListener('settingschanged', RIL.textSettingsChanged, false, true)
  2499.             }
  2500.             }
  2501.         }
  2502.         
  2503.         
  2504.         }
  2505.         
  2506.     }
  2507.     
  2508.     },
  2509.     
  2510.     checkForNetError : function(doc) {
  2511.     if (!doc) return;
  2512.     
  2513.     if (doc.baseURI.match('about:neterror'))
  2514.     {
  2515.         doc.netError = true;
  2516.         let item = RIL.checkDocument(doc);
  2517.         if (item)
  2518.         {
  2519.         RIL.netErrors[item.itemId] = true;
  2520.         }
  2521.     }
  2522.     },
  2523.     
  2524.     getItemForCurrentPage : function()
  2525.     {
  2526.     return RIL.getItemForUri(getBrowser().currentURI);
  2527.     },
  2528.     
  2529.     getItemForDocument : function(doc)
  2530.     {
  2531.     return RIL.getItemForUri(RIL.APP.uri(doc.location), doc);
  2532.     },
  2533.     
  2534.     getItemForUri : function(uri, document)
  2535.     {
  2536.     try {
  2537.     let item = false;
  2538.     let itemId;
  2539.     let type;
  2540.     let offline = false;
  2541.     document = document ? document : content.document
  2542.         
  2543.     if (uri.scheme == 'file')
  2544.     {
  2545.         if ( decodeURI( uri.spec ).match( RIL.APP.ASSETS.PAGES_FOLDER_NAME ))
  2546.         {
  2547.         
  2548.         // check against offline paths
  2549.         uri.spec.match( new RegExp('\\W'+RIL.APP.ASSETS.PAGES_FOLDER_NAME+'\\W(-?[0-9]*)\\W([a-z]*)\.') );
  2550.         itemId = RegExp.$1;
  2551.         type = RegExp.$2 == 'text' ? 1 : 2;
  2552.  
  2553.         // If the content document is branded with an item, make sure the item still exists in the list
  2554.         // before using it
  2555.         if (document && document.RIL_item)
  2556.         {
  2557.             item = RIL.APP.LIST.itemByUrl( document.RIL_item.url );
  2558.         }
  2559.         else if (itemId)
  2560.         {
  2561.             item = RIL.APP.LIST.itemById(itemId);
  2562.         }
  2563.         offline = true;
  2564.         }
  2565.         
  2566.     }
  2567.     else if (uri.scheme == 'chrome' && uri.spec.match(/chrome:\/\/isreaditlater\/content\/textError.html\?page\=(-?[0-9]*)/) && RIL.APP.errorPackages[RegExp.$1])
  2568.     {
  2569.         // If the content document is branded with an item, make sure the item still exists in the list
  2570.         // before using it
  2571.          if (document && document.RIL_item)
  2572.         {
  2573.         item = RIL.APP.LIST.itemByUrl( document.RIL_item.url );
  2574.         }
  2575.         else
  2576.         {
  2577.         item = document.RIL_item ? document.RIL_item : RIL.APP.errorPackages[RegExp.$1].item;
  2578.         }
  2579.         type = -1;
  2580.     }
  2581.     else
  2582.     {
  2583.         item = RIL.APP.LIST.itemByUrl( uri.spec );
  2584.         type = 2;
  2585.         }
  2586.     return {item:item, type:type, offline:offline};
  2587.     } catch(e) { Components.utils.reportError(e); }
  2588.     },
  2589.     
  2590.     updateLocationBarButtons : function(t) {
  2591.     var l;
  2592.     var m1;
  2593.     var m2;
  2594.     
  2595.     if (RIL.APP.listHasBeenLoadedOnce && !RIL.APP.listError)
  2596.     {
  2597.         switch(t) {
  2598.         case(1): 
  2599.             l=false;
  2600.             m1=true;
  2601.             RIL.lastURL = RIL.currentURL();
  2602.             break;
  2603.         case(2): 
  2604.             l=true;
  2605.             m1=false;
  2606.             break;
  2607.         default:
  2608.             l=true;
  2609.             m1=true;                
  2610.         }
  2611.     }
  2612.     else
  2613.     {
  2614.         l = true;
  2615.         m1 = true;
  2616.     }
  2617.     
  2618.     if (RIL.xul('urlbar_add', true))         { RIL.xul('urlbar_add', true).hidden = l; }
  2619.     if (RIL.xul('urlbar_mark', true))         { RIL.xul('urlbar_mark', true).hidden = m1; }
  2620.     
  2621.     this.updateStatusBarIcon('textStatusButton', 'showStatusIconText', m1);
  2622.     this.updateStatusBarIcon('shareStatusButton', 'showStatusIconShare', m1, true);
  2623.     this.updateStatusBarIcon('clickToSaveButton', 'showStatusIconClick', m1);
  2624.     
  2625.     },
  2626.     
  2627.     updateStatusBarIcon : function(id, pref, m1, persist)
  2628.     {
  2629.     let pref, hidden;
  2630.     if (RIL.xul(id, true))
  2631.     {
  2632.         pref = RIL.PREFS.get(pref);
  2633.         hidden = pref == 'show' ? false : ( pref == 'hide' ? true : (persist && content.document.wasItemOnce ? false : m1)  );
  2634.         RIL.xul(id, true).hidden = hidden;
  2635.     }
  2636.     },
  2637.     
  2638.     // -- //
  2639.     
  2640.     addCurrent : function(skipDownloadViews) {
  2641.     // Check if the document was branded because the url may be masked on offline pages
  2642.     let url;
  2643.     if (content.document.RIL_item)
  2644.     {
  2645.         url = content.document.RIL_item.url;
  2646.     }
  2647.     else
  2648.     {
  2649.         url = RIL.currentURL();
  2650.     }
  2651.     if (!RIL.APP.checkIfValidUrl( url, true )) return false;
  2652.     
  2653.     // Add it
  2654.     let itemId = RIL.APP.LIST.add( {url:url, title:RIL.currentTitle()} );
  2655.     
  2656.     // If the content document brand exists, update it's item id
  2657.     if (content.document.RIL_item)
  2658.         content.document.RIL_item.itemId = itemId;
  2659.     
  2660.     RIL.checkPage();
  2661.     RIL.startWatchingScrollingForItemId(itemId, content.document, true);
  2662.     
  2663.     RIL.addNewItemToDownload(RIL.APP.LIST.itemById(itemId), skipDownloadViews);
  2664.     
  2665.     return itemId;
  2666.     },
  2667.     
  2668.     addedFromLocationBar : function()
  2669.     {
  2670.         if (RIL.PREFS.getBool('autoCloseTab') && gBrowser.browsers.length > 1)
  2671.         gBrowser.removeCurrentTab();  
  2672.     },
  2673.     
  2674.     addReadItem : function(item, itemId, batch)
  2675.     {
  2676.     // remove from readList
  2677.     //RIL.readList.list[ RIL.readList.iByItemId[ itemId ] ] = false;
  2678.     RIL.APP.LIST.readListNeedsRefresh();
  2679.     
  2680.     item.timeUpdated = RIL.APP.now();
  2681.     RIL.APP.LIST.add(item, batch);
  2682.     },
  2683.  
  2684.     markCurrentAsRead : function() {
  2685.     try {
  2686.         let currentItem = RIL.getItemForCurrentPage();
  2687.         
  2688.         try {
  2689.         // Post mark as read action:
  2690.         if (!RIL.PREFS.getBool('autoMark')) // actions not allowed when auto mark is enabled, otherwise it can chain them together to remove the entire list one by one
  2691.         {
  2692.             let action = RIL.PREFS.get('mark');
  2693.             if (action == 'next') RIL.readNextItemInList(true);
  2694.             else if (action == 'rand') RIL.readSomethingRandom();
  2695.             else if (action == 'close' && gBrowser.browsers.length > 1) gBrowser.removeCurrentTab();
  2696.         }
  2697.         } catch(e) {Components.utils.reportError(e);}
  2698.         
  2699.         // brand the document with the item so we can re-add offline pages correctly
  2700.         //if (currentItem.offline || currentItem.type == -1)
  2701.         //{
  2702.                 RIL.updateFilteredListIndex();
  2703.                 currentItem.item.oldFilteredIndex = RIL.filteredListIndex[currentItem.item.itemId];
  2704.         content.document.RIL_item = currentItem.item;
  2705.         //}
  2706.         
  2707.         // brand the document with a flag that it used to be an item
  2708.         content.document.wasItemOnce = true;
  2709.         
  2710.         RIL.APP.LIST.mark( currentItem.item.itemId );
  2711.         RIL.APP.commandInAllOpenWindows('RIL', 'checkPage', null, true);
  2712.         
  2713.     } catch(e) {Components.utils.reportError(e);}    
  2714.     },
  2715.     
  2716.     autoMark : function()
  2717.     {
  2718.     let currentItem = RIL.getItemForCurrentPage();
  2719.     if (currentItem && currentItem.item && currentItem.item.itemId == RIL.autoMarkItemId)
  2720.         RIL.markCurrentAsRead();
  2721.     },
  2722.     
  2723.     saveLink : function(url, title, tags) {
  2724.     
  2725.     // Last ditch attempts to find the url and/or title
  2726.     url     =  url && gContextMenu     ? gContextMenu.linkURL ? gContextMenu.linkURL : (gContextMenu.link ? gContextMenu.link : url) : url;
  2727.     title     = !title && gContextMenu     ? gContextMenu.linkText()    : title;
  2728.     
  2729.     // Clean up    
  2730.     if (!this.APP.checkIfValidUrl(url)) return false;
  2731.     title = this.APP.trim(this.APP.stripTags(title));
  2732.     
  2733.     // Save the link immediately so the user sees it in their list, then launch a thread
  2734.     // to follow through and resolve it
  2735.     
  2736.     let itemId = this.APP.LIST.add({url:url, title:title});
  2737.     let savedItem;
  2738.     
  2739.     if (!itemId)
  2740.         savedItem = this.APP.LIST.itemByUrl(url);    
  2741.     
  2742.     if (itemId || savedItem && tags)
  2743.         this.APP.LIST.saveTags(savedItem ? savedItem.itemId : itemId, tags);
  2744.     
  2745.     if (itemId)
  2746.         this.APP.resolveLink(itemId, url, this.APP.resolveLinkCallback);
  2747.     
  2748.     this.addNewItemToDownload(this.APP.LIST.itemById(itemId));    
  2749.     },
  2750.     
  2751.     saveTabs : function() {
  2752.     let num, i, b;
  2753.     num = gBrowser.browsers.length;
  2754.     for (i = 0; i < num; i++) {
  2755.         b = gBrowser.getBrowserAtIndex(i);
  2756.         RIL.APP.LIST.add( {url:b.currentURI.spec , title:b.contentTitle}, true );
  2757.     }
  2758.     RIL.APP.LIST.endBatchAndRefresh();
  2759.     },
  2760.     
  2761.     
  2762.     // -- Scrolling -- //
  2763.     
  2764.     startWatchingScrollingForItemId : function(itemId, doc, saveCurrentNow) {
  2765.     doc.itemId = itemId;
  2766.     
  2767.     // watch scrolling
  2768.     if (!RIL.APP.LIST.pendingScrollPositions) RIL.APP.LIST.pendingScrollPositions = {};
  2769.     doc.addEventListener('scroll', RIL.scrolled, false);
  2770.     
  2771.     if (saveCurrentNow)
  2772.         RIL.scrolled();
  2773.     },
  2774.     
  2775.     scrolled : function(e, immediateFlush) {    
  2776.     let w = content.window;
  2777.     let d = content.document;
  2778.     
  2779.     if (!RIL.currentItem || !RIL.currentItem.item) return;
  2780.     
  2781.     if (RIL.currentItem.type == 1)
  2782.     {
  2783.         // text view scrolling
  2784.         let windowWidth = w.innerWidth;
  2785.         let windowHeight = w.innerHeight;
  2786.         let high     = d.elementFromPoint(windowWidth/2, 20);
  2787.         let low     = d.elementFromPoint(windowWidth/2, 20+25);
  2788.         let iH     = high.getAttribute('nodeIndex');
  2789.         let iL     = low.getAttribute('nodeIndex');
  2790.         
  2791.         let e, i;
  2792.         if (iH && iH*1 > iL*1) {
  2793.         e = high;
  2794.         i = iH;
  2795.         } else if (iL) {
  2796.         e = low;
  2797.         i = iL;    
  2798.         }
  2799.         
  2800.         if (i)
  2801.         {
  2802.         RIL.APP.LIST.pendingScrollPositions[RIL.currentItem.item.itemId + 2] = {
  2803.             itemId: RIL.currentItem.item.itemId,
  2804.             view: 1,
  2805.             section: d.body.id=='more' ? 1 : 0,
  2806.             nodeIndex: i,
  2807.             percent: Math.ceil( w.pageYOffset + w.innerHeight ) / d.body.scrollHeight * 100
  2808.         }
  2809.         }
  2810.  
  2811.     }
  2812.     else
  2813.     {
  2814.         // fallback scrolling
  2815.         RIL.APP.LIST.pendingScrollPositions[RIL.currentItem.item.itemId + 2] = {
  2816.         itemId: RIL.currentItem.item.itemId,
  2817.         view: 2,
  2818.         nodeIndex: w.pageYOffset,
  2819.         percent: Math.ceil( w.pageYOffset + w.innerHeight ) / d.body.scrollHeight * 100
  2820.         }
  2821.     }
  2822.     
  2823.     RIL.APP.clearTimeout(RIL.APP.LIST.pendingScrollPositionsTO);
  2824.     if (immediateFlush) RIL.APP.LIST.flushScrollPositions();
  2825.     else
  2826.     RIL.APP.LIST.pendingScrollPositionsTO = RIL.APP.setTimeout(RIL.APP.LIST.flushScrollPositions, RIL.APP.timeToWaitBeforeFlushingScrollPositions, RIL.APP.LIST);
  2827.     },
  2828.     
  2829.     
  2830.     // -- Click to Save Mode -- //
  2831.     
  2832.     toggleClickToSaveMode : function()
  2833.     {    
  2834.     !this.findNotificationBox() ? this.clickToSaveOn() : this.clickToSaveOff();    
  2835.     },
  2836.     
  2837.     clickToSaveOn : function()
  2838.     {
  2839.     // Close click to save and then reopen if it's already open
  2840.     if (!this.findNotificationBox()) this.clickToSaveOff();
  2841.     
  2842.     //
  2843.     content.document.addEventListener("click", this.clickSaveCallback, false);        
  2844.  
  2845.     RIL.currentNotification = this.displayNotificationForClickMode();
  2846.     },
  2847.     
  2848.     clickToSaveOff : function()
  2849.     {
  2850.     RIL.removeNotificationBox();
  2851.     content.document.removeEventListener("click", RIL.clickSaveCallback, false);
  2852.     RIL.currentNotification = null;
  2853.     },
  2854.     
  2855.     displayNotificationForClickMode : function()
  2856.     {
  2857.     let msg     = this.l('ClickModeNotify');
  2858.     let name    = 'ISRILclickmode';
  2859.     let icon    = 'chrome://isreaditlater/skin/clicksave.png';
  2860.     buttons = [{
  2861.             accessKey : '',
  2862.             label:  this.l('tags'),
  2863.             popup: 'RIL_clickToSaveTagsPanel'
  2864.         },{
  2865.             label:  this.l('TurnOff'),
  2866.             popup: null,
  2867.             callback: this.clickToSaveOff
  2868.         }];
  2869.     this.xul('clickToSaveTags').value = '';
  2870.     
  2871.     return this.getNotificationBox(name, msg, icon, buttons);
  2872.     },
  2873.     
  2874.     clickSaveCallback: function(e) {
  2875.     let targ, url, newNode, link, X, Y, title;
  2876.     
  2877.     // Determine which link was clicked
  2878.     if (gContextMenu) {
  2879.         
  2880.         link = RIL.bubbleToTagName(gContextMenu.target, 'A');
  2881.         
  2882.     } else {
  2883.         
  2884.         if (!e) var e = window.event;
  2885.         if (e.target) targ = e.target;
  2886.         else if (e.srcElement) targ = e.srcElement;
  2887.         link = RIL.bubbleToTagName(targ, 'A');
  2888.         e.preventDefault();
  2889.         
  2890.     }
  2891.     
  2892.     // If we found a link, save it
  2893.     if (link && (RIL.findNotificationBox() || gContextMenu) ) {
  2894.     
  2895.         if (RIL.PREFS.get('link-checks') != 'no') {
  2896.         newNode = RIL.getClickSavedNode();
  2897.         
  2898.         if (e) {
  2899.             X = e.pageX;
  2900.             Y = e.pageY;
  2901.         } else {
  2902.             coords = RIL.findPos(link);
  2903.             X = coords[0];
  2904.             Y = coords[1];
  2905.         }
  2906.         newNode.style.left = (X + 10)+"px";
  2907.         newNode.style.top = (Y - 15)+"px";    
  2908.         }
  2909.         
  2910.         // Get the link anchor text
  2911.         title = RIL.APP.stripTags(link.innerHTML);
  2912.         if (title.length == 0) {
  2913.         if (link.firstChild) {
  2914.             
  2915.             //If it doesn't have content then it's probably an image link, so check the image for a title or alt label first
  2916.             if (link.firstChild.title.length > 0) {
  2917.             title = link.firstChild.title;
  2918.             } else if (link.firstChild.alt.length > 0) {
  2919.             title = link.firstChild.title;
  2920.             }
  2921.         }
  2922.         }
  2923.         
  2924.         RIL.saveLink(link.href, title, RIL.xul('clickToSaveTags').value);
  2925.     }
  2926.     },    
  2927.     
  2928.     getClickSavedNode : function() {
  2929.     
  2930.     if (!RIL.clickModeNodesCounter) RIL.clickModeNodesCounter = 0;
  2931.     
  2932.     let newNode = content.document.createElement('div');
  2933.     let id = 'readitlatersaved' + RIL.clickModeNodesCounter;
  2934.     
  2935.     newNode.setAttribute('id', id);
  2936.     newNode.setAttribute('style', 'width:24px;height:24px;position:absolute;font-size:10px;font-weight:bold;color:#000000;background:url(\'chrome://isreaditlater/skin/book24-windows.png\') no-repeat;z-index:100000');
  2937.     
  2938.     content.document.body.appendChild(newNode);
  2939.     
  2940.     //if (RIL.PREFS.get('link-checks') == 'hide') {
  2941.         setTimeout(RIL.APP.genericDataClosure(RIL, 'removeClickSavedNode', id), 1500);
  2942.     //}
  2943.     
  2944.     RIL.clickModeNodesCounter++;
  2945.     
  2946.     return newNode;
  2947.     },
  2948.     
  2949.     removeClickSavedNode : function(id) {
  2950.     this.removeNode(content.document.getElementById(id));
  2951.     },
  2952.     
  2953.     setTagsForClickToSave : function(notification, desc)
  2954.     {
  2955.     this.currentNotification.label = this.l('ClickModeNotify');
  2956.     if (this.xul('clickToSaveTags').value.length > 0)
  2957.         this.currentNotification.label += ' Items will be tagged with ' + this.xul('clickToSaveTags').value;
  2958.     },
  2959.  
  2960.     
  2961.     // --- Notification Box --- //
  2962.     
  2963.     getNotificationBox : function(name, msg, icon, buttons) {        
  2964.     let notificationBox = gBrowser.getNotificationBox();
  2965.     if (!notificationBox.getNotificationWithValue(name))
  2966.         return notificationBox.appendNotification(msg, name, icon, notificationBox.PRIORITY_WARNING_MEDIUM, buttons);    
  2967.     },
  2968.     
  2969.     findNotificationBox : function(name) {
  2970.     let name = name ? name : 'ISRILclickmode';
  2971.     let notificationBox = gBrowser.getNotificationBox();
  2972.     if (notificationBox.getNotificationWithValue(name))
  2973.         return notificationBox;
  2974.     
  2975.     },
  2976.     
  2977.     removeNotificationBox : function(name) {    
  2978.     let notificationBox = this.findNotificationBox();
  2979.     if (notificationBox)
  2980.         notificationBox.removeCurrentNotification();
  2981.     },
  2982.     
  2983.     
  2984.     
  2985.     // -- Text -- //
  2986.     
  2987.     retrieveAndOpenTextForCurrentUrl : function(refresh)
  2988.     {
  2989.     let currentItem = RIL.getItemForCurrentPage();
  2990.     let item = currentItem.item;
  2991.     let url = item ? item.url : 'nonexist';
  2992.     
  2993.     RIL.retrieveAndOpenTextForUrl(url, 'current', content.document, refresh);    
  2994.     },
  2995.     
  2996.     retrieveAndOpenTextForUrl : function(url, target, doc, refresh, title)
  2997.     {
  2998.     let item = RIL.APP.LIST.itemByUrl( url );
  2999.     let itemId;
  3000.     
  3001.     if (target == 'current')
  3002.     {
  3003.         let currentItem = RIL.getItemForCurrentPage();
  3004.         
  3005.         // if text view is currently open, ask if they'd like to refresh it
  3006.         if ( (currentItem.type == 1 && currentItem.offline) || currentItem.type == -1)
  3007.         {
  3008.         if (currentItem.item.url == url)
  3009.         {
  3010.             if (RIL.APP.PROMPT.confirm(window, 'Read It Later', RIL.l('redownloadText')))
  3011.             {
  3012.             refresh = true;
  3013.             if (item)
  3014.                 url = item.url;
  3015.             }
  3016.             else { 
  3017.             return;
  3018.             }
  3019.         }
  3020.         }
  3021.     }
  3022.     
  3023.     // if item is not in the user's list, then add it before fetching
  3024.  
  3025.     if (!item)
  3026.     {
  3027.         if (target == 'current')
  3028.         itemId = RIL.addCurrent( {text:true} );
  3029.         else
  3030.         itemId = RIL.APP.LIST.add( {url:url, title:title} );
  3031.         
  3032.         // get item object
  3033.         item = RIL.APP.LIST.itemById( itemId );
  3034.         
  3035.         if (!item) return; // failed to save page to list
  3036.     }
  3037.     
  3038.     
  3039.     // check if text has already been downloaded
  3040.     if (item.offlineText == 1 && (!refresh || !navigator.onLine ))
  3041.     {
  3042.         RIL.openTextViewForUrl(url, target);
  3043.     }    
  3044.     
  3045.     // if not
  3046.     else {            
  3047.         
  3048.         if (target != 'current')
  3049.         {
  3050.         RIL.openUrl('chrome://isreaditlater/content/loadText.html?url='+url, null, target);
  3051.         return;
  3052.         }
  3053.         
  3054.         // a text view is already being loaded here, so cancel the request
  3055.         if (content.document.getElementById('RIL_TEXT_SPINNER'))
  3056.         {
  3057.         return false;
  3058.         }
  3059.         
  3060.         // throw a spinner
  3061.         let spinner = content.document.createElement('div');
  3062.         spinner.style.position = 'fixed';
  3063.         spinner.style.zIndex = '999999999';
  3064.         spinner.style.left = '0px';
  3065.         spinner.style.top = '0px';
  3066.         spinner.style.width = '100%';
  3067.         spinner.style.height = '100%';
  3068.         spinner.style.opacity = '0.85';
  3069.         spinner.style.background = '#FFFFFF url(chrome://isreaditlater/skin/syncing.png) center no-repeat';
  3070.         spinner.setAttribute('id', 'RIL_TEXT_SPINNER');
  3071.         content.document.body.appendChild(spinner)
  3072.         
  3073.         // fetch        
  3074.         RIL.APP.OFFLINE.downloadTextWrapper( item.itemId, item.url, RIL, doc );        
  3075.         
  3076.     }
  3077.     
  3078.     },
  3079.     
  3080.     textViewReady : function(downloader, doc)
  3081.     {    
  3082.     RIL.APP.LIST.updateOffline(downloader.itemId, downloader.type, downloader.statusCode);
  3083.     
  3084.     //if (!downloader.success)
  3085.     //{
  3086.     //    RIL.APP.PROMPT.alert(window, 'Read It Later', "There was a problem getting the text view:\n\n"+downloader.textRequest.error);
  3087.     //    downloader.doc.location.reload();
  3088.     //} else {    
  3089.         RIL.openTextViewForUrl( downloader.url, null, doc, null );
  3090.     //}
  3091.     },
  3092.     
  3093.     openTextViewForUrl : function(url, target, doc, error)
  3094.     {
  3095.     let target = target ? target : 'current';    
  3096.     let item = RIL.APP.LIST.itemByUrl(url);
  3097.     let path, linkpath;
  3098.         
  3099.     if (item.offlineText != 1)
  3100.     {
  3101.         let errorMessage;
  3102.         
  3103.         if (error)
  3104.         {
  3105.         // not really sure how it could ever get here, but probably a good catch just in case
  3106.         errorMessage = '<p>The text generator could not reach the original article.</p></p>'+error+'</p>';
  3107.         } else if (item.offlineText == 0)
  3108.         {
  3109.         // not really sure how it could ever get here, but probably a good catch just in case
  3110.         errorMessage = '<p>A text view has not been downloaded for this page.  Click the text icon in the status bar to download the text view</p>';
  3111.         }
  3112.         else if (item.offlineText == 403)
  3113.         {
  3114.         errorMessage = '<p>The text generator could not reach the original article.</p>\
  3115.                 <p>You may have been trying to download too many articles at once.  Please wait a little while before trying again.</p>\
  3116.                 <p>Otherwise, if you continue you have problems, <a href="http://readitlaterlist.com/support/">please let me know</a>.  Thanks!</p>';
  3117.         }
  3118.         else if (!navigator.onLine)
  3119.         {
  3120.         errorMessage = '<p>Read It Later was not able to reach the original article the last time it tried to download this page.</p>\
  3121.                 <p>Next time you are connected to the internet, double check that the page is still available online and try then again.</p>\
  3122.                 <p>Otherwise, if you continue you have problems, <a href="http://readitlaterlist.com/support/">please let me know</a>.  Thanks!</p>'; 
  3123.         }
  3124.         else
  3125.         {        
  3126.         errorMessage = '<p>The text generator could not reach the original article.</p>\
  3127.                 <p>You can see if the site is down by viewing the article here: <a target="_blank" href="'+item.url+'">'+item.url+'</a></p>\
  3128.                 <p>If you are able to view the original article, try generating the text again.</p>\
  3129.                 <p>Otherwise, if you continue you have problems, <a href="http://readitlaterlist.com/support/">please let me know</a>.  Thanks!</p>';
  3130.         }
  3131.         
  3132.         RIL.APP.errorPackages[item.itemId] = {
  3133.         errorMessage: errorMessage,
  3134.         item : item
  3135.         }
  3136.         
  3137.         // Load error template - when loaded it will fire an event to fill the page
  3138.         linkpath = path = 'chrome://isreaditlater/content/textError.html?page='+item.itemId;
  3139.         
  3140.     }
  3141.     else
  3142.     {            
  3143.         linkpath = this.APP.ASSETS.folderPathForItemId( item.itemId, true ) + 'text.html';
  3144.         path = this.APP.ASSETS.folderPathForItemId( item.itemId ) + 'text.html';        
  3145.     }
  3146.         
  3147.     if (doc)
  3148.     {
  3149.         //if doc still has url loaded (and wasn't changed/closed)
  3150.         doc.location.href = linkpath;
  3151.     }
  3152.     else
  3153.     {
  3154.         this.openUrl(path , null, target );
  3155.     }
  3156.     },
  3157.     
  3158.     textSettingsChanged : function(e)
  3159.     {    
  3160.     // get the document
  3161.     let o = content.document.body.getAttribute('o');
  3162.     
  3163.     if (o)
  3164.         RIL.PREFS.set('text-options', o);    
  3165.     },
  3166.     
  3167.     loadOfflineWebView : function(itemId, inDocument)
  3168.     {    
  3169.     let path = RIL.APP.ASSETS.folderPathForItemId( itemId , true ) + 'web.html';
  3170.     
  3171.     if (inDocument)
  3172.         inDocument.location = path;    
  3173.     else
  3174.         setTimeout(RIL.APP.genericDataClosure(RIL, 'openUrl', path), 10);
  3175.     },
  3176.     
  3177.     
  3178.     
  3179.     
  3180.     // -- Navigation -- //
  3181.     
  3182.     openUrl : function(url, offline, targ, ref) {
  3183.     targ = ((targ)?(targ):('current'));
  3184.     let w = this.inSidebar ? RILsidebar.w : mainWindow;
  3185.     w.openUILinkIn(url, targ, null, null, ref?RIL.APP.uri(ref):null);
  3186.     },
  3187.     
  3188.     
  3189.     
  3190.     // --  Observers -- //
  3191.         
  3192.  
  3193.     urlBarListener : {
  3194.         QueryInterface: function(aIID) {
  3195.             if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
  3196.                aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
  3197.                aIID.equals(Components.interfaces.nsISupports))
  3198.             return this;
  3199.             throw Components.results.NS_NOINTERFACE;
  3200.         },
  3201.         
  3202.         onLocationChange: function(aProgress, aRequest, aURI) {
  3203.         RIL.checkPage(aURI);
  3204.         },    
  3205.         
  3206.         onStateChange: function() {},    
  3207.         onStatusChange: function() {},
  3208.         onProgressChange: function() {},
  3209.         onSecurityChange: function() {},
  3210.         onLinkIconAvailable: function() {},
  3211.     },
  3212.     
  3213.     
  3214.     // -- Overall XUL Layout -- //
  3215.     
  3216.     
  3217.     
  3218.     addToToolbar : function() {
  3219.  
  3220.     // --- Add Buttons if not on UI --- //
  3221.     let id = this.XULnamespace + 'toolbar_button';
  3222.     if (!this.PREFS.getBool('toolbar-btn-added') && !this.xulId(id, true)) {
  3223.         let navbar = this.xulId("nav-bar", true);
  3224.         if (navbar)
  3225.         {
  3226.         let curSet = navbar.currentSet;
  3227.         if (curSet.indexOf( id ) == -1) {
  3228.             var set = curSet + "," + id;
  3229.             navbar.setAttribute("currentset", set);
  3230.             navbar.currentSet = set;
  3231.             document.persist("nav-bar", "currentset");
  3232.             BrowserToolboxCustomizeDone(true);
  3233.         }
  3234.         this.PREFS.set('toolbar-btn-added', true);
  3235.         }
  3236.     }    
  3237.         
  3238.     },
  3239.     
  3240.     refreshToolbarCountStatus : function()
  3241.     {
  3242.     if (RIL.PREFS.getBool('show-count'))
  3243.     {
  3244.         RIL.updateUnreadCount();
  3245.         RIL.addClass(RIL.xul('toolbar_button'), 'RIL_show_count');        
  3246.     }
  3247.     else
  3248.     {
  3249.         RIL.removeClass(RIL.xul('toolbar_button'), 'RIL_show_count');        
  3250.     }
  3251.     },
  3252.     
  3253.     updateUnreadCount : function() {
  3254.     if (RIL.xul('toolbar_button') && RIL.APP.LIST.list)
  3255.         RIL.xul('toolbar_button').setAttribute('unread', RIL.APP.LIST.list.length);
  3256.     },
  3257.     
  3258.     
  3259.     // -- Key set -- //
  3260.     
  3261.     setupKeyStrokes : function() {
  3262.     let keySet = this.xulId('mainKeyset', true);
  3263.     if (keySet && !this.xul('key_toggle', true)) {
  3264.         
  3265.         this.newKey(keySet, 'hotkey_toggle', "RIL.hotKeyToggle()" );
  3266.         this.newKey(keySet, 'hotkey_push', "RIL.hotKeyPush()" );
  3267.         this.newKey(keySet, 'hotkey_open_list', "RIL.hotKeyOpenList()" ) 
  3268.         this.newKey(keySet, 'hotkey_click_mode', "RIL.hotKeyClickMode()" );
  3269.         
  3270.         //sidebar
  3271.         var sKey = this.newKey(keySet, 'hotkey_sidebar', "toggleSidebar('RIL_sidebarlist');" );
  3272.         if (sKey)
  3273.         sKey.command = 'RIL_sidebarlist';            
  3274.         
  3275.         //sidebar menu option
  3276.         if ( this.xulId('viewSidebarMenu', true) ) {
  3277.             this.xulId('viewSidebarMenu', true).appendChild( this.createNode('menuitem', {
  3278.             key:        this.XULnamespace + 'hotkey_sidebar',
  3279.             observes:    'RIL_sidebarlist'
  3280.             } ) );                
  3281.         }
  3282.     }
  3283.     },    
  3284.     
  3285.     formatKey : function(modifiers, key, keycode) {
  3286.     modifiers = ((modifiers)?(modifiers.split(' ').join(' + ') + ' + '):(''));
  3287.     return modifiers + ((key)?(key):(keycode));        
  3288.     },
  3289.     
  3290.     newKey : function(keySet, key, oncommand) {
  3291.     
  3292.     let currentKey = RIL.xul(key);
  3293.     if (currentKey) this.removeNode(currentKey);
  3294.     
  3295.     let keyPair;
  3296.     let pref = RIL.PREFS.get(key);
  3297.     if (pref && pref.length) {
  3298.         keyPair = pref.split('||');
  3299.         key = this.createNode('key', {
  3300.         id:this.XULnamespace + key,
  3301.         modifiers:keyPair[0],
  3302.         oncommand:oncommand
  3303.         } );
  3304.         key.setAttribute( ((keyPair[1].length > 1)?('keycode'):('key')) , keyPair[1]);
  3305.         
  3306.         keySet.appendChild(key);
  3307.         
  3308.         return key;
  3309.     }
  3310.     },
  3311.     
  3312.     hotKeyToggle : function() {
  3313.     let currentItem = RIL.getItemForCurrentPage();
  3314.     if (currentItem && currentItem.item)
  3315.     {
  3316.         RIL.markCurrentAsRead();
  3317.     }
  3318.     else
  3319.     {
  3320.         RIL.addCurrent();
  3321.     }
  3322.     RIL.checkPage();
  3323.     },
  3324.     hotKeyPush : function() {
  3325.     return RIL.readSomething();
  3326.     },
  3327.     hotKeyOpenList : function() {
  3328.     RIL.toggleReadingList();
  3329.     },    
  3330.     hotKeyClickMode : function() {
  3331.     RIL.toggleClickToSaveMode();
  3332.     },    
  3333.     
  3334.     hotkeyText : function(p) {
  3335.     let key;
  3336.     let keycode;
  3337.     let keySet = RIL.PREFS.get('hotkey_'+p).split('||');
  3338.     if (keySet[0].length > 1) {
  3339.         key = null;
  3340.         keycode = keySet[1];
  3341.     } else {
  3342.         key = keySet[1];
  3343.         keycode = null;
  3344.     }
  3345.     return RIL.formatKey( keySet[0], key, keycode );
  3346.     },
  3347.     
  3348.     
  3349.     
  3350.     // -- XUL Helpers -- //
  3351.     
  3352.     // inMainWindow  means it should be in the main window, not the current document scope
  3353.     // for example: document inside of a sidebar is different then document inside of the main window
  3354.     xul : function(id, inMainWindow) {
  3355.     let doc = inMainWindow && this.inSidebar && RILsidebar ? RILsidebar.w.document : document;
  3356.         return doc.getElementById( this.XULnamespace + id );
  3357.     },
  3358.     
  3359.     xulId : function(id, inMainWindow) {
  3360.     let doc = inMainWindow && this.inSidebar && RILsidebar ? RILsidebar.w.document : document;
  3361.     return doc.getElementById( id );    
  3362.     },
  3363.     
  3364.     createNode : function (ty, attributes, htmlNamespace) {
  3365.         var its;
  3366.         var node = htmlNamespace ? document.createElementNS('http://www.w3.org/1999/xhtml', 'html:'+ty) : document.createElement(ty);
  3367.         for(var ik in attributes) {
  3368.             switch(ik) {
  3369.                 case('class'):
  3370.                     node.className = attributes[ik];
  3371.                     break;
  3372.                 case('style'):
  3373.                     if (typeof attributes[ik] == 'string') {
  3374.                         node.setAttribute('style', attributes[ik]);
  3375.                     } else {
  3376.                         its = attributes[ik].split(';');
  3377.                         for(var i=0; i<its.length; i++) {
  3378.                             parts = its[i].split(':');
  3379.                             node.style[parts[0]] = parts[1];
  3380.                         }
  3381.                     }
  3382.                     break;
  3383.                 case('innerHTML'):
  3384.                     node.innerHTML = attributes[ik];
  3385.                     break;
  3386.                 case('onclick'):
  3387.                     if (typeof attributes[ik] == 'string') {
  3388.                         node.setAttribute('onclick', attributes[ik]);
  3389.                     } else {
  3390.                         node.onclick = attributes[ik]
  3391.                     }
  3392.                 default:
  3393.                     node.setAttribute(ik, attributes[ik]);
  3394.             }                
  3395.         }
  3396.         return node;
  3397.     },
  3398.     
  3399.     fillSelect : function(obj, min, max, selected)
  3400.     {
  3401.     if (!obj) return;
  3402.     selected = selected ? selected - min : 0 ;
  3403.     obj.removeAllItems();
  3404.     
  3405.     for(let i=min; i<=max; i++)
  3406.     {
  3407.         obj.insertItemAt(i - 1, i, i);
  3408.     }
  3409.     obj.setAttribute('min', min);
  3410.     obj.selectedIndex = (selected > max - 1) ? (max - 1) : selected;
  3411.     },
  3412.     
  3413.     clearChildren : function(parent, cls) {
  3414.     for(var i=parent.childNodes.length - 1; i >= 0; i--) {
  3415.         if (!cls || (cls && parent.childNodes[i].className.match(cls))) {
  3416.         parent.removeChild(parent.childNodes.item(i));
  3417.         }
  3418.     }    
  3419.     },
  3420.     
  3421.     removeNode : function(node) {
  3422.     if (node) { return node.parentNode.removeChild(node); }
  3423.     },
  3424.     
  3425.     addClass : function(node, cls) {
  3426.     if (node != null && !node.className.match(cls)) node.className += ' ' + cls; 
  3427.     },
  3428.     
  3429.     removeClass : function(node, cls) {
  3430.     if (node != null) node.className = node.className.replace(cls,'');
  3431.     },
  3432.     
  3433.     // ewww... word-wrap: break-word would be much nicer..
  3434.     // #bug 520617 - https://bugzilla.mozilla.org/show_bug.cgi?id=520617
  3435.     wbrThisString : function(str, obj)
  3436.     {
  3437.     let longRegex = /[a-z0-9_\?\%\=\&]{30}/i;
  3438.     
  3439.     if (!str.match(longRegex))
  3440.     {
  3441.         obj.textContent = str;
  3442.     } else
  3443.     {
  3444.         // Walk through string and add wbr tags at the end of each long text section        
  3445.         let strRemainder = str;        
  3446.         let match = longRegex.exec(strRemainder);
  3447.         let stringPart;
  3448.         
  3449.         while(match)
  3450.         {           
  3451.         stringPart = strRemainder.substr(0, match.index) + match[0];
  3452.         this.addWbrToObjectForString(obj, stringPart);
  3453.         
  3454.         strRemainder = strRemainder.substr(match.index + match[0].length)
  3455.         match = longRegex.exec(strRemainder);
  3456.         }
  3457.         
  3458.         this.addWbrToObjectForString(obj, strRemainder);
  3459.         
  3460.     }
  3461.     },
  3462.     
  3463.     addWbrToObjectForString : function(obj, string)
  3464.     {        
  3465.     let part = RIL.createNode('span', false, true);
  3466.     part.textContent = '|'+string;
  3467.     obj.appendChild( part );
  3468.     obj.appendChild( RIL.createNode('wbr' , false, true ) );    
  3469.     },
  3470.     
  3471.     htmlDecode : function(str)
  3472.     {
  3473.     // TODO : find a faster/more effective method for this
  3474.     return !str.match('&') ? str : str.replace(/"/gi, '"').replace(/&/gi, '&') ;
  3475.     },
  3476.     
  3477.     whatIsTheClickTarget : function(e, defaultPref) {
  3478.     var targ;
  3479.         let userSetting = false;
  3480.     if (e.which != 3) {
  3481.         if (e.which == 1) {
  3482.         if (e.ctrlKey || RIL.keyStates[224]) {//224
  3483.             targ = 'tab';
  3484.             RIL.keyStates[ 17 ] = false; //FF doesn't run keyup event when clicking
  3485.         } else if (e.shiftKey || RIL.keyStates[16]) {
  3486.             targ = 'window';
  3487.             RIL.keyStates[ 16 ] = false; //FF doesn't run keyup event when clicking
  3488.         } else if (defaultPref) {            
  3489.             targ = RIL.PREFS.get(defaultPref);
  3490.                         userSetting = true;
  3491.         }
  3492.         } else if (e.which == 2) {
  3493.         targ = 'tab';    
  3494.         }
  3495.     }
  3496.     return {targ:targ,which:e.which,userSetting:userSetting};
  3497.     },
  3498.         
  3499.     findPos : function (obj) {
  3500.     let curleft = curtop = 0;
  3501.     if (obj.offsetParent) {
  3502.         curleft = obj.offsetLeft
  3503.         curtop = obj.offsetTop
  3504.         while (obj = obj.offsetParent) {
  3505.         curleft += obj.offsetLeft
  3506.         curtop += obj.offsetTop
  3507.         }
  3508.     }
  3509.     return [curleft,curtop];
  3510.     },
  3511.     
  3512.     bubbleToTagName : function(obj, tagName) {
  3513.     let maxlvls = 10;
  3514.     let lvl = 1;
  3515.     while (obj.tagName != tagName) {
  3516.         if (obj.parentNode) { 
  3517.         obj = obj.parentNode;
  3518.         }
  3519.         if (lvl >= maxlvls) { return false; }
  3520.         lvl++;
  3521.     }
  3522.     return obj;
  3523.     },
  3524.     
  3525.     keyDown : function(e) {
  3526.     RIL.keyStates[ e.keyCode ] = true;
  3527.     },
  3528.     keyUp : function(e) {
  3529.     RIL.keyStates[ e.keyCode ] = false;        
  3530.     },
  3531.     
  3532.     contextPopupShowing : function() {
  3533.     if (RIL.PREFS.getBool('context-menu')) {
  3534.         if ( (gContextMenu.onSaveableLink || ( gContextMenu.inDirList && gContextMenu.onLink )) ) {
  3535.         gContextMenu.showItem(RIL.XULnamespace + "context_saveLink", true);
  3536.         gContextMenu.showItem(RIL.XULnamespace + "context_savePage", false);
  3537.         gContextMenu.showItem(RIL.XULnamespace + "context_clickMode", false);
  3538.         } else if (gContextMenu.isTextSelected) {
  3539.         gContextMenu.showItem(RIL.XULnamespace + "context_saveLink", false);
  3540.         gContextMenu.showItem(RIL.XULnamespace + "context_savePage", false);
  3541.         gContextMenu.showItem(RIL.XULnamespace + "context_clickMode", false);
  3542.         } else if (!gContextMenu.onTextInput) {
  3543.         gContextMenu.showItem(RIL.XULnamespace + "context_saveLink", false);
  3544.         gContextMenu.showItem(RIL.XULnamespace + "context_savePage", true);
  3545.         gContextMenu.showItem(RIL.XULnamespace + "context_clickMode", true);
  3546.         } else {
  3547.         gContextMenu.showItem(RIL.XULnamespace + "context_saveLink", false);
  3548.         gContextMenu.showItem(RIL.XULnamespace + "context_savePage", false);
  3549.         gContextMenu.showItem(RIL.XULnamespace + "context_clickMode", false);
  3550.         }
  3551.     } else {
  3552.         gContextMenu.showItem(RIL.XULnamespace + "context_saveLink", false);
  3553.         gContextMenu.showItem(RIL.XULnamespace + "context_savePage", false);
  3554.         gContextMenu.showItem(RIL.XULnamespace + "context_clickMode", false);
  3555.     }
  3556.     },
  3557.     
  3558.     
  3559.     // -- Document Helpers -- //
  3560.     
  3561.     currentURL : function(checkForOffline) { //checkForOffline will look for the real url if the currentURL is an offline url
  3562.     if (checkForOffline) {
  3563.         let currentItem = RIL.getItemForCurrentPage();
  3564.         if (currentItem && currentItem.item && currentItem.offline)
  3565.         {
  3566.         return currentItem.item.url;
  3567.         }
  3568.     }
  3569.     return ( (!getBrowser().currentURI.spec) ? (false):(getBrowser().currentURI.spec) );
  3570.     },
  3571.     currentTitle : function() {
  3572.     return getBrowser().contentDocument.title;
  3573.     },    
  3574.     getSidebarWidth : function (w) {
  3575.         return window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
  3576.             .getInterface(Components.interfaces.nsIWebNavigation)
  3577.             .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
  3578.             .rootTreeItem
  3579.             .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
  3580.             .getInterface(Components.interfaces.nsIDOMWindow).document.getElementById("sidebar-box").width;
  3581.    },
  3582.     
  3583.     setSidebarWidth : function (w) {
  3584.         window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
  3585.             .getInterface(Components.interfaces.nsIWebNavigation)
  3586.             .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
  3587.             .rootTreeItem
  3588.             .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
  3589.             .getInterface(Components.interfaces.nsIDOMWindow).document.getElementById("sidebar-box").width=w;
  3590.    },
  3591.    
  3592.    
  3593.     
  3594.     // -- Helpers -- //
  3595.     
  3596.     
  3597.     l : function(id){
  3598.         //this.APP.d(id);
  3599.     return this.xul('strings', true).getString(id);
  3600.     }
  3601.  
  3602.         
  3603.  
  3604. }
  3605. RIL = new RIL();
  3606.  
  3607. window.addEventListener("load", function() {RIL.init();}, false);
  3608. window.addEventListener("unload", function() {RIL.uninit();}, false);
  3609.  
  3610.